1 类型转换构造函数
1.1 why?
基本类型之间的转换,编译器内置转换规则:int -> double
类类型之间的转换,编译器不知道转换规则,需要用户提供:Cat -> Dog
// consconv_why.cpp 为什么需要自定义转换
#include <iostream>
using namespace std;
class Cat {
public:
Cat( const char* name ) : m_name(name) {
//【string m_name(name);】
}
void talk( ) {
cout << m_name << ": 喵喵~~~" << endl;
}
private:
string m_name;
};
class Dog {
public:
Dog( const char* name ) : m_name(name) {
//【string m_name(name);】
}
void talk( ) {
cout << m_name << ": 汪汪~~~" << endl;
}
private:
string m_name;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
Cat smallwhite("小白");
smallwhite.talk( );
Dog bigyellow = smallwhite; // Cat --> Dog
return 0;
}
1.2 理论
定义:1)单参构造 (同于拷贝构造函数)
2)参数类型与类类型不同 (异于拷贝构造函数)
(单参构造就2种,参数类型同于类类型,就是拷贝构造,否则就是类型转换构造)
class 目标类型 {
目标类型 ( const 源类型& src ) { ... } // 类型转换构造函数
};
用于:
1)利用一个已定义的对象,来定义另一个不同类型的对象
2)实现从源类型到目标类型的隐式类型转换
通过explicit关键字,可以强制 这种通过类型转换构造函数实现的类型转换 必须通过静态转换显示地进行:
class 目标类型 {
explicit 目标类型 ( const 源类型& src ) { ... };
};
// consconv.cpp
// 类型转换构造函数 -- 指定 源类型 到 目标类型 的 转换规则
#include <iostream>
using namespace std;
class Cat {
public:
explicit Cat( const char* name ) : m_name(name) { // 类型转换构造函数
//【string m_name(name);】
cout << "Cat类的类型转换构造函数被调用" << endl;
}
void talk( ) {
cout << m_name << ": 喵喵~~~" << endl;
}
private:
string m_name;
friend class Dog; // 友元声明
};
class Dog {
public:
Dog( const char* name ) : m_name(name) { // 类型转换构造函数
//【string m_name(name);】
}
explicit Dog( const Cat& c ) : m_name(c.m_name) { // 类型转换构造函数(Cat-->Dog的转换规则)
//【string m_name=c.m_name;】
cout << "Dog类的类型转换构造函数被调用" << endl;
}
void talk( ) {
cout << m_name << ": 汪汪~~~" << endl;
}
private:
string m_name;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
// Cat smallwhite("小白"); // 定义smallwhite,利用smallwhite.Cat("小白")->类型转换构造函数
// Cat smallwhite = "小白"; // 定义 匿名Cat类对象,利用 匿名Cat类对象.Cat("小白")->隐式转换
// Cat smallwhite=匿名Cat类对象-->克隆
Cat smallwhite = static_cast<Cat>("小白");
// 定义 匿名Cat类对象,利用 匿名Cat类对象.Cat("小白")->静态转换
// Cat smallwhite=匿名Cat类对象-->克隆
smallwhite.talk( );
// Dog bigyellow(smallwhite); // 定义bigyellow,利用bigyellow.Dog(smallwhite)->类型转换构造函数
// Dog bigyellow = smallwhite; // 定义 匿名Dog类对象,利用 匿名Dog类对象.Dog(smallwhite)->隐式类型转换
// Dog bigyellow = 匿名Dog类对象-->克隆
Dog bigyellow = static_cast<Dog>(smallwhite);
// 定义 匿名Dog类对象,利用 匿名Dog类对象.Dog(smallwhite)->静态类型转换
// Dog bigyellow = 匿名Dog类对象-->克隆
bigyellow.talk( );
return 0;
}
2 析构函数
2.1 理论
析构函数的函数名就是在类名前面加“~”,没有返回类型也没有参数,不能重载。
在销毁对象之前一刻自动被调用,且仅被调用一次 :
- 对象离开作用域 (栈对象离开main函数)
- delete操作符 (堆对象被释放)
作用:销毁 对象的各个成员变量
如果一个类没有定义析构函数,那么编译器会为其提供一个默认的析构函数:
- 对基本类型的成员变量,什么也不做。
- 对类类型的成员变量,调用相应类型的析构函数。
- 销毁 对象的各个成员变量
2.2 析构过程
对象的销毁过程:
1)调用析构函数(陷)
- 执行自己在析构函数中书写的代码
- 利用类成员变量调用相应的析构函数
- 释放对象的各成员变量所占内存空间
2)释放整个对象所占用的内存空间 (皮)
// 析构函数
#include <iostream>
using namespace std;
class Human {
public:
// 如果类没有提供任何构造函数,编译器将提供一个无参的构造函数
/* Human() {
【int m_age;】定义m_age,初值为随机数
【string m_name;】定义m_name,利用m_name.string()
}*/
Human( int age=0, const char* name="无名" ) : m_age(age),m_name(name) {
//【int m_age=age;】定义m_age,初值为age
//【string m_name(name);】定义m_name,利用m_name.string(name)
cout << "Human类缺省构造函数被调用" << endl;
}
// 如果类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数
/* Human( const Human& that ) {
【int m_age=that.m_age;】定义m_age,初值为that.m_age
【string m_name(that.m_name);】定义m_name,利用m_name.string(that.m_name)-->string类拷贝构造函数
}*/
Human( const Human& that ) : m_age(that.m_age), m_name(that.m_name) {
//【int m_age=that.m_age;】定义m_age,初值为that.m_age
//【string m_name(that.m_name);】定义m_name,利用m_name.string(that.m_name)
cout << "Human类拷贝构造函数被调用" << endl;
}
// 如果类没有提供拷贝赋值函数,编译器将提供一个默认的拷贝赋值函数
/* Human& operator=( const Human& that ) {
this->m_age = that.m_age;
this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string类的拷贝赋值函数
return *this;
}*/
Human& operator=( const Human& that ) {
// 编译器不会再拷贝赋值函数中塞任何操作
cout << "Human类的拷贝赋值函数被调用" << endl;
this->m_age = that.m_age;
this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string类的拷贝赋值函数
return *this;
}
// 如果类没有提供析构函数,编译器将提供一个默认的析构函数
/* ~Human() {
对于基本类型成员变量m_age,什么都不做
对于类类型成员变量m_name,利用 m_name.~string()
释放 m_age/m_name 本身所占内存空间
}*/
~Human() {
cout << "Human类的析构函数被调用" << endl;
// 对于基本类型成员变量m_age,什么都不做
// 对于类类型成员变量m_name,利用 m_name.~string()
// 释放 m_age/m_name 本身所占内存空间
}
void getinfo( ) {
cout << "姓名: " << m_name << ", 年龄: " << m_age << endl;
}
private:
int m_age; // 基本类型的成员变量
string m_name; // 类类型的成员变量
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
Human h; // 定义h,利用h.Human()-->h维护的内容为(无名,0)
h.getinfo( );
Human h2(22,"张飞"); // 定义h2,利用h2.Human(22,"张飞")-->h2维护的内容为(张飞,22)
h2.getinfo();
Human h3(h2); // = h2; // 定义h3,利用h3.Human(h2)-->触发拷贝构造函数
h3.getinfo();
Human h4; // 定义h4,利用h4.Human()-->h4维护的内容为(无名,0)
cout << "h4被赋值前---";
h4.getinfo();
h4 = h3; // h4.operator=(h3)-->触发拷贝赋值函数
cout << "h4被赋值后---";
h4.getinfo();
cout << "------------main will be over----------------" << endl;
return 0;
} //(1) h.~Human() h2.~Human() h3.~Human() h4.~Human() (2)释放h/h2/h3/h4本身所占的内存空间
//(1) 删馅 (2)删皮
2.3 has to
通常情况下,若对象在其声明周期的最终时刻,并不持有任何动态分配的资源,可以不定义析构函数。
若对象在其声明周期的最终时刻,持有动态资源,则必须定义析构函数,释放对象所持有的动态资源。
// hastodes必须自己写析构函数的情况 -- 对象临死时,持有动态资源
#include <iostream>
using namespace std;
class A {
public:
A(int i) : m_i(i), m_p(new int), m_f(open("./file", O_CREAT|O_RDWR,0644)) {
//【int m_i=i;】定义m_i,初值为i
//【int* m_p=new int;】定义m_p,初值为指向一块堆内存(动态资源)
//【int m_f=open(...);】定义m_f,初值为文件描述符-->文件表等内核结构(动态资源)
}
~A() {
delete m_p;
close( m_f ); // 动态资源需要自己写代码释放
// 释放m_i / m_p / m_f 本身所占内存空间
}
/* 默认析构函数
~A() {
释放m_i / m_p / m_f 本身所占内存空间
}
*/
private:
int m_i;
int* m_p;
int m_f;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
A a; // 定义a,利用a.A()
return 0;
} // a.~A() 释放a本身所占内存空间
析构函数的功能并不局限在释放资源上,它还可以执行 我们希望在 对象被释放之前 执行的任何操作。
3 深拷贝
3.1 浅拷贝缺陷
如果类不提供拷贝构造,编译器将提供默认的拷贝构造。
无论是 拷贝构造 还是 拷贝赋值,其默认实现,对任何类型的指针成员都是简单地复制地址,而并不复制地址指向的数据,这种情况称为浅拷贝。(左图)
为了获得完整意义上的对象副本,必须自己定义 拷贝构造 和 拷贝赋值,针对指针型成员变量做深拷贝。(右图)
// copybytes_pre.cpp 类中有指针成员,默认拷贝构造 会有浅拷贝缺陷
#include <iostream>
#include <cstring>
using namespace std;
// 模拟C++标准的string类 实现自己的String类
class String {
public:
String( const char* psz="" ) : m_psz(new char[strlen(psz)+1]) {
//【char* m_psz=new char[strlen(psz)+1];】// 动态资源
strcpy( m_psz, psz );
}
~String( /* String* this */ ) {
delete[] this->m_psz;
// 释放 m_psz 本身所占内存空间
}
char* c_str() { return m_psz; }
// 默认的拷贝构造
/* String( const String& that ) {
【char* m_psz = that.m_psz;】只复制了地址,没有复制地址指向的数据-->浅拷贝
}*/
private:
char* m_psz;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
String s1("hello");
cout << "s1:" << s1.c_str() << ", s1中的m_psz指向的堆内存的地址: " << (void*)s1.c_str() << endl;
String s2(s1); // = s1; 定义s2,利用s2.String(s1)-->拷贝构造函数
cout << "s2:" << s2.c_str() << ", s2中的m_psz指向的堆内存的地址: " << (void*)s2.c_str() << endl;
return 0;
} // s1.~String() s2.~String()
相对于拷贝构造,拷贝赋值需要做更多的工作:
- 避免自赋值
- 分配新资源
- 拷贝新内容
- 释放旧资源
- 返回自引用
// copybytes.cpp 类中有指针成员,默认拷贝构造 会有浅拷贝缺陷
#include <iostream>
#include <cstring>
using namespace std;
// 模拟C++标准的string类 实现自己的String类
class String {
public:
String( const char* psz="" ) : m_psz(new char[strlen(psz)+1]) {
//【char* m_psz=new char[strlen(psz)+1];】// 动态资源
strcpy( m_psz, psz );
}
~String( /* String* this */ ) {
delete[] this->m_psz;
// 释放 m_psz 本身所占内存空间
}
char* c_str() { return m_psz; }
// 默认的拷贝构造
/* String( const String& that ) {
【char* m_psz = that.m_psz;】只复制了地址,没有复制地址指向的数据-->浅拷贝
}*/
// 深拷贝构造函数
String( const String& that ) : m_psz(new char[strlen(that.m_psz)+1]) {
//【char* m_psz = new char[strlen(that.m_psz)+1];】
strcpy( m_psz, that.m_psz ); // 不复制地址,复制地址指向的数据-->深拷贝
}
/* 默认拷贝赋值函数
String& operator=( const String& that ) {
this->m_psz = that.m_psz; // 只复制了地址,没有复制地址指向的数据-->浅拷贝
return *this;
}
*/
// 深拷贝赋值函数
String& operator=( const String& that ) {
if( this != &that ) { // 防止自赋值
delete[] this->m_psz; // 释放旧资源
this->m_psz = new char[strlen(that.m_psz)+1]; // 分配新资源
strcpy( this->m_psz, that.m_psz ); // 拷贝新内容
}
return *this; // 返回自引用
}
private:
char* m_psz;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
String s1("hello");
cout << "s1:" << s1.c_str() << ", s1中的m_psz指向的堆内存的地址: " << (void*)s1.c_str() << endl;
String s2(s1); // = s1; 定义s2,利用s2.String(s1)-->拷贝构造函数
cout << "s2:" << s2.c_str() << ", s2中的m_psz指向的堆内存的地址: " << (void*)s2.c_str() << endl;
String s3; // 定义s3,利用s3.String()-->s3维护一个字节堆内存('\0')
s3 = s2; // s3.operator=(s2)
cout << "s3:" << s3.c_str() << ", s3中的m_psz指向的堆内存的地址: " << (void*)s3.c_str() << endl;
return 0;
} // s1.~String() s2.~String()
3.2 建议
1)只有类中有指针型成员变量,才会涉及深浅拷贝的问题,因此应尽量避免使用指针型成员变量。
2)如果确实无法实现完整意义上的 深拷贝拷贝构造 和 深拷贝拷贝赋值,可将它们私有化,禁止用户使用。
4 静态成员
4.1 静态成员变量
静态成员变量 不属于对象 而 属于类:
- 静态成员变量不包含在对象中,进程级生命期
- 静态成员变量的定义和初始化,只能在类的外部(即全局域)而不能在构造函数中进行。
- 静态成员变量依然受 类作用域 和 访问控制限定符 的约束。
- 访问静态成员变量,既可以通过 类 也可以通过 对象。
- 静态成员变量为该类的所有对象实例所共享。
// static.cpp 类的静态成员变量
#include <iostream>
using namespace std;
// 普通成员变量:属于对象,对象的生命期 静态成员变量:不属于对象,进程级生命期
class A {
public:
A() {
//【int m_i;】
}
int m_i; // 声明
static int m_si; // 声明
};
int A::m_si = 0; // 全局域中定义-->进程级生命期
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
A a, b; // 静态成员变量没有保存在对象内部-->不属于对象
cout << "a对象的大小:" << sizeof(a) << endl; // 4
cout << "b对象的大小:" << sizeof(b) << endl; // 4
A::m_si = 888; // 静态成员受到类作用域的约束 也受到访问控制限定符的约束-->属于类
a.m_si = 999; // A::m_si=999;
cout << "b.m_si=" << b.m_si << endl; // A::m_si
// 类的静态成员变量,被该类的所有对象共享
return 0;
}
4.2 静态成员函数
静态成员函数 不属于对象(普通成员函数也不属于对象,更准确地说,不用非要对象来调) 而 属于类:
- 静态成员函数没有this指针,也没有常属性
- 静态成员函数依然受 类作用域 和 访问控制限定符的约束
- 访问静态成员函数,既可以通过 类 也可以通过 对象。(普通成员函数,只对象)
-静态成员函数只能访问静态成员,而非静态成员函数可以访问所有成员。
// static.cpp 类的 静态成员变量 和 静态成员函数
#include <iostream>
using namespace std;
// 普通成员函数:必须利用对象来调用 静态成员函数:不是必须利用对象来调用
class A {
public:
int m_i; // 普通成员变量
void foo( /* const A* this */ ) const { // 普通成员函数
cout << "foo is invoked" << endl;
cout << m_i << endl; // ok
cout << m_si << endl;// ok
bar(); // ok
// 以上三行代码证明 非静态成员函数 即可访问非静态成员 也可访问 静态成员(不挑食)
}
static int m_si; //静态成员变量
static void bar( /*无this指针*/ ) /*const*/ { // 静态成员函数
cout << "bar is invoked" << endl;
cout << m_si << endl; // ok
// cout << m_i << endl; // error
// foo(); // error
// 以上三行代码证明 静态成员函数 只能访问 静态成员,不能访问非静态的普通成员(挑食)
}
};
int A::m_si = 0; // 全局域中定义-->进程级生命期
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
A a, b;
a.foo(); // foo(&a);
b.foo(); // foo(&b);
A::bar(); // 受到类作用域的约束 也受到访问控制限定符的约束-->属于类
a.bar(); // A::bar();
b.bar(); // A::bar();
return 0;
}
4.3 总结
事实上,类的静态成员变量和静态成员函数,更像是普通的全局变量和全局函数,
只是多了一层类作用域和访问控制限定符的约束,
相当于 具有成员访问属性的全局变量和全局函数。
5 类 扩充
空类对象的大小是1个字节。
类中不能包含 本类对象 作为 普通成员变量;
类中可以包含 本类对象 作为 静态成员变量。
// class_add
#include <iostream>
using namespace std;
/*
class A { // 空类
};
int main( void ) {
A a; // 空类对象占1个字节内存(1个字节的垃圾数据)
A& ra = a;
cout << "空类对象a的大小: " << sizeof(a) << endl;
return 0;
}
*/
class A {
public:
int m_i;
// A m_a; // error
static A m_sa; // ok
};
int main( void ) {
A a; // 定义a(给a分配内存空间)
cout << "对象a的大小: " << sizeof(a) << endl;
return 0;
}
6 单例模式
要求:设计一个类,要求用户在使用这个类时仅有一个实例(只能出现一个对象):
class Singleton {
// 设计这个类
};
int main(void) {
// 用户这里只能出现一个Singleton类对象,不能出现第二个
return 0;
}
实现方法:
1)将 包括类的拷贝构造函数在内的所有构造函数 私有化,防止user在类的外部创建对象。
2)唯一的对象由类的设计者来创建
3)提供公有静态成员函数getInstance()使用户可以获取到唯一对象。
单例分类:
1)饿汉式:无论用不用,程序启动即创建 hungry.cpp (不推荐)
2)懒汉式:用的时候创建,不用了即销毁 lazy.cpp (推荐)
// hungry_singleton.cpp
// 单例模式--要求设计一个类型,用户在使用这个类时只能出现一个对象
#include <iostream>
using namespace std;
// 恶汉式单例
class Singleton {
public://4 //5
static Singleton& getInstance( ) {
return s_instance;
}
private:
Singleton( ) {} // 1
Singleton( const Singleton& that ) {} // 6
static Singleton s_instance; // 2 唯一对象
};
Singleton Singleton::s_instance; // 3
// 以上代码模拟类的设计者
// -----------------------
// 以下代码模拟类的使用者
int main( void ) {
Singleton& s1 = Singleton::getInstance( );
Singleton& s2 = Singleton::getInstance( );
Singleton& s3 = Singleton::getInstance( );
cout << "&s1: " << &s1 << ", &s2: " << &s2 << ", &s3: " << &s3 << endl;
return 0;
}
// lazy_singleton.cpp
// 单例模式:设计一个类,保证用户在使用这个类时,只能出现一个对象
#include <iostream>
using namespace std;
// 懒汉式单例:单例高级实现手法
class Singleton {
public:
static Singleton& getInstance( ) {
if( s_instance==NULL ) {
s_instance = new Singleton; // 唯一的对象
cout << "创建了唯一的对象" << endl;
}
++s_counter;
return *s_instance;
}
void releaseInstance( ) {
if( --s_counter == 0 ) {
delete s_instance;
s_instance = NULL;
cout << "销毁了唯一的对象" << endl;
}
}
private:
Singleton() {}
Singleton( const Singleton& that ) {}
static Singleton* s_instance; // 并不是唯一对象,仅仅是一个指针而已
static int s_counter; // 计数功能
};
Singleton* Singleton::s_instance = NULL; // 程序刚刚时,唯一的对象不存在
int Singleton::s_counter = 0;
// 以上的代码模拟类的设计者(例如:类库、被人设计的类、自己的设计的类)
// -------------------------------------
// 以下的代码模拟用户(使用类的人)
int main( void ) {
Singleton& s1 = Singleton::getInstance(); // 第一次调用getInstance函数时,创建唯一的对象
Singleton& s2 = Singleton::getInstance(); // 以后再调用getInstance函数时,返回第一次调用时创建的对象
Singleton& s3 = Singleton::getInstance(); // ...
cout << "&s1: " << &s1 << ", &s2: " << &s2 << ", &s3: " << &s3 << endl;
s1.releaseInstance( ); //
s2.releaseInstance( ); //
s3.releaseInstance( ); // 最后一次调用releaseInstance才将对象销毁
return 0;
}