目录六
十七.拷贝构造和拷贝赋值
2.拷贝赋值
eg.A a1;
A a2 = a1; //拷贝构造
A a3;
a3 = a1; //拷贝赋值
①当两个对象进行赋值操作时,比如“i3=i2”,编译器会自动将其处理为i3.operator=(i3)成员函数调用形式,其中"operator="被称为拷贝赋值操作符函数,由该函数实现两个对象复制,其返回结果就是表达式结果。
②如果自己没有定义拷贝赋值函数,那么编译器会为类提供一个缺省的拷贝赋值函数,但是缺省的拷贝赋值函数是浅拷贝,可能会有数据共享、double free、内存泄漏等问题,
//举例:
int* i2 = new int(100);
int* i3 = new int(0);
i3 = i2;
//(相当于将i3直接指向了i2这块内存,数据上是对的,正确赋值了,但delete时会报错double free,原来i3上的内存就造成了泄露)
如下图:
为了避免这些问题,必须自己定义一个深拷贝赋值函数(注意按照下面的格式):
类名& operator=(const 类名& that){
if(&that != this){//防止自赋值
//释放旧内存;
//分配新内存;
//拷贝新数据;
}
return *this;//返回自引用
}
eg.
#include <iostream>
using namespace std;
class Integer{
public:
Integer(int i):m_pi(new int(i)){
//m_pi = new int(i);
}
void print(void) const {
cout << *m_pi << endl;
}
~Integer(void){
cout << "析构函数:" << m_pi << endl;
delete m_pi;
}
//缺省的拷贝构造函数(浅拷贝)
/*Integer(const Integer& that){
cout << "缺省的拷贝构造函数" << endl;
m_pi = that.m_pi;
}*/
//自定义拷贝构造函数(深拷贝)
Integer(const Integer& that){
cout << "自定义拷贝构造函数" << endl;
m_pi = new int;
*m_pi = *that.m_pi;
}
//缺省拷贝赋值函数(浅拷贝)
//i3=i2 ==> i3.operator=(i2)
/*Integer& operator=(const Integer& that){
cout << "缺省拷贝赋值函数" << endl;
if(&that != this){//防止自赋值
m_pi = that.m_pi;
}
return *this;//返回自引用
}*/
//自定义拷贝赋值函数(深拷贝)
Integer& operator=(const Integer& that){
cout << "自定义拷贝赋值函数" << endl;
if(&that != this){//防止自赋值
delete m_pi;//释放旧内存
m_pi = new int;//分配新内存
*m_pi = *that.m_pi;//拷贝新数据
}
return *this;//返回自引用
}
private:
int* m_pi;
};
int main(void){
Integer i1(100);
Integer i2(i1);//拷贝构造
i1.print();//100
i2.print();//100
Integer i3(0);
//i3.operator=(i2);
i3 = i2;//拷贝赋值
i3.print();//100
return 0;
}
练习
练习:参考上一节的String类,除了实现字符串类(String)里包括构造函数、析构函数、拷贝构造函数以外,增加拷贝赋值函数。
#include <iostream>
#include <cstring>
#pragma warning( disable : 4996 )
using namespace std;
class String
{
public:
//构造函数:支持const char*字符串:动态分配内存
String(const char* str = NULL)
{
m_str = new char[strlen(str ? str : "") + 1]; //防止str是空的字符串,就会报错;动态分配内存,在原有字符串长度基础上+1
strcpy(m_str, str ? str : "");//字符串拷贝赋值操作,同样防止str为空的情况
}
//析构函数:主要负责清理对象生命周期中的动态资源
~String(void)
{
//cout << "析构函数:" << &m_str << endl;
cout << "析构函数:" << (void*)m_str << endl; // 打印delete的地址
if (m_str) // 若字符串是空的则跳过,若不是空的则要释放内存
{
delete[] m_str; //因为申请的内存是char[],则释放时是delete[]
m_str = NULL;
}
}
//拷贝构造函数
String(const String& that)
{
m_str = new char[strlen(that.m_str) + 1];
strcpy(m_str, that.m_str);
//*m_str = *that.m_str; //这么写相当于只复制了字符串中的第一个元素,与11copy.cpp里面不一样,那里是int类型
}
//拷贝赋值
//str2 = str3 ==> str2.operator=(str3)
String& operator=(const String& that)
{
if (&that != this) //防止自赋值
{
//方法一:推荐
delete[] m_str;//释放旧内存
m_str = new char[strlen(that.m_str) + 1];//分配新内存
strcpy(m_str, that.m_str);//拷贝新数据
//方法二
//有的人认为方法一会出现释放旧内存后,新内存分配失败,会导致程序崩溃,虽然这可能性很低。
//方法二的思想是先分配一块新内存,然后释放旧内存,即将方法一的步骤倒一下
/*char* str = new char[strlen(that.m_str)+1];
delete[] m_str;
m_str = strcpy(str,that.m_str);*/
//方法三:
//String temp(that);//经过拷贝构造,临时变量里面的指针指向的是新分配的内存,数据也是拷贝后的新的字符串
//swap(m_str,temp.m_str);//swap()函数用于交换两个指针的指向,相当于str2指针指向新内存新字符串,临时对象的指针指向旧内存旧字符串
}
return *this; //返回自引用
}
//打印函数
void print(void)const
{
cout << m_str << endl;
}
private:
char* m_str;
};
int main(void)
{
String str("buding");
str.print();
String str2 = "duoduo";
str2.print();
String str3 = "dummer";
str2 = str3;
str2.print();
return 0;
}
十八.静态成员(static)
1.静态成员变量
class 类名
{
static 数据类型 变量名;//声明
};
数据类型 类名::变量名 = 初值;//定义和初始化
①普通成员变量属于对象,而静态成员变量不属于对象,静态成员变量内存在数据段(全局区),可以把静态成员变量理解为是被限制在类的内部去使用的全局变量。
②普通成员变量需要在构造时定义和初始化,而静态成员变量需要在类的外部单独定义和初始化。
③使用方法
类名::静态成员变量;(推荐) eg.A::s_data
对象名.静态成员变量;和上面等价(不推荐,容易和普通成员变量混淆) eg.a.s_data
eg.
#include <iostream>
using namespace std;
class A{
public:
//普通成员变量在构造时定义和初始化
A(int data = 0):m_data(data){}
int m_data;//声明普通成员变量
static int s_data;//声明静态成员变量
//const和static同时修饰的成员变量,需要在声明时初始化(特殊,了解)
const static int cs_data = 0;//ok
};
//静态成员变量需要在类的外部单独定义和初始化
int A::s_data = 20;
int main(void){
A a(10);
cout << "size=" << sizeof(a) << endl;//4
//普通成员变量是属于对象的一部分,必须通过对象才能访问
cout << a.m_data << endl;//10
//cout << A::m_data << endl;//error
//静态成员变量即使没有对象时,也可以通过"类名::"可以直接访问
cout << A::s_data << endl;//20
cout << a.s_data << endl;//ok
/*下面是一道笔试题,普通成员变量没有变,静态成员变量变了
A a2(10);
a2.m_data = 11;
a2.s_data = 22;
cout << a.m_data << endl;//10
cout << a.s_data << endl;//22*/
return 0;
}
2.静态成员函数
class 类名{
static 返回类型 函数名(形参表){
函数体;
}
};
①静态成员函数没有this指针,也没有常属性(即不能加const),可以把它理解为是被限制在类的内部使用的全局函数。
②使用方法
类名::静态成员函数(实参表);(推荐)
对象名.静态成员函数(实参表);//和上面等价(不推荐,容易和普通成员函数混淆)
(注:在静态成员函数中只能访问静态成员,因为静态成员函数没有this指针;在非静态成员函数中既可以访问静态成员,也可以访问非静态成员)
eg.
#include <iostream>
using namespace std;
class A{
public:
A(void):m_data(10){}
static void func1(void){
cout << "静态成员函数" << endl;
//在静态成员函数中只能访问静态成员,因为其没有this指针
//cout << m_data << endl;//error
cout << s_data << endl;
}
void func2(void) const {
cout << "非静态成员函数" << endl;
//在非静态成员函数中既可以访问静态成员,也可以访问非静态成员
cout << m_data << endl;
cout << s_data << endl;
}
int m_data;
static int s_data;
};
int A::s_data = 20;
int main(void){
A::func1();//不用对象,可以通过类名直接调用静态成员函数
//A::func2();//error,无法通过类名调用非静态成员函数
A a;
a.func2();//ok,通过对象,可以调用非静态成员函数,也可以调用静态成员函数
a.func1();//ok
return 0;
}
3.单例模式
①概念
一个类只允许存在唯一的对象,并提供它的访问方法。
②实现思路
–》禁止在类的外部创建对象:私有化构造函数,同时将缺省的构造函数也声明为私有
–》类的内部维护唯一的对象:静态成员变量
–》提供单例对象的访问方法:静态成员函数
③创建方式
–》饿汉式:单例对象无论用或不用,程序启动即创建
eg. static A m_a;
优点:代码实现简单,多线程安全,访问使用效率高(以空间换时间)
缺点:浪费内存
eg.编程思路:单例对象无论用或不用,程序启动即创建。
#include <iostream>
using namespace std;
//单例模式:饿汉式
class Singleton{
public:
//3)使用静态成员函数获取单例对象
static Singleton& getInstance(void){
return s_instance;
}
void print(void) const{
cout << m_data << endl;
}
private:
//1)私有化构造函数
Singleton(int data):m_data(data){
cout << "单例对象被创建了" << endl;
}
//将缺省的拷贝构造函数声明为私有
Singleton(const Singleton&);
//2)使用静态成员变量来维护唯一的对象
static Singleton s_instance;//声明
private:
int m_data;
};
Singleton Singleton::s_instance(123);//定义(静态成员变量需要在类的外部单独定义和初始化)
int main(void){
//Singleton s(123);
//Singleton* ps = new Singleton(123);
cout << "main开始运行" << endl;
Singleton& s1 = Singleton::getInstance();
Singleton& s2 = Singleton::getInstance();
Singleton& s3 = Singleton::getInstance();
s1.print();
s2.print();
s3.print();
cout << "&s1=" << &s1 << endl;
cout << "&s2=" << &s2 << endl;
cout << "&s3=" << &s3 << endl;
//Singleton s4 = s1;//拷贝构造,应该error
//cout << "&s4=" << &s4 << endl;
return 0;
}
–》懒汉式:单例对象用时再创建,不用即销毁
eg. static A* m_a;
优点:节省内存(以时间换空间)
缺点:访问使用效率低,多线程中需要加锁保护,代码实现复杂
eg.编程思路:单例对象用时再创建,不用即销毁(单线程示例)
#include <iostream>
using namespace std;
//单例模式:懒汉式
class Singleton {
public:
//3)使用静态成员函数获取单例对象
static Singleton& getInstance(void) {
if (s_instance == NULL)//只有当s_instance为空时再new一块空间创建单例对象,否则即直接返回*s_instance,即防止多次new,保证一个类只允许存在唯一的对象的单例对象
{
s_instance = new Singleton(123);
}
++s_count;//s_count = s_count + 1,每次调用getInstance()函数,即表示一个使用者,则计数+1
return *s_instance;
}
//单例对象不用即销毁,销毁时机?
//所有的使用者都不再使用,才能销毁
//这里销毁不用析构函数,因为析构函数销毁的是对象在执行时产生的动态资源内容,而我们这里要销毁的是对象自身
void release(void) {
if (--s_count == 0) {
delete s_instance; //析构函数在delete后执行
s_instance = NULL;
}
}
void print(void) const {
cout << m_data << endl;
}
private:
//1)私有化构造函数
Singleton(int data) :m_data(data) {
cout << "单例对象被创建了" << endl;
}
//将缺省的拷贝构造函数声明为私有
Singleton(const Singleton&);
//析构函数
~Singleton(void) {
cout << "单例对象被销毁了" << endl;
}
//2)使用静态成员变量来维护唯一的对象
static Singleton* s_instance;//声明,这里是用指针的形式,与饿汉式不同
//计数:记录单例对象使用者的个数
static int s_count;
private:
int m_data;
};
//这里也和饿汉式不同,先初始化s_instance为空指针(即可认为是:程序启动后,只有一个空指针,单例对象还未被创建,只是有一个可以指向单例对象的指针而已。)
Singleton* Singleton::s_instance = NULL;
//当一个共享内存被多个使用者使用时,逻辑应该是当最后一个人不再使用时,共享内存再被释放
int Singleton::s_count = 0;
int main(void) {
//Singleton s(123);
//Singleton* ps = new Singleton(123);
cout << "main开始运行" << endl;
//++s_count:1,new
Singleton& s1 = Singleton::getInstance();
//++s_count:2
Singleton& s2 = Singleton::getInstance();
//++s_count:3
Singleton& s3 = Singleton::getInstance();
s1.print();
s1.release();//--s_count:2
s2.print();
s3.print();
cout << "&s1=" << &s1 << endl;
cout << "&s2=" << &s2 << endl;
cout << "&s3=" << &s3 << endl;
s2.release();//--s_count:1
s3.release();//--s_count:0,delete
//程序运行保证第一次调取getInstance()时创建单例对象,最后一次调取release()时销毁单例对象。
return 0;
}
eg.(多线程示例)
//如果报错无法打开源文件pthread.h
//解决办法:https://blog.csdn.net/qq_37058219/article/details/83382690
#include <iostream>
#include <pthread.h>
using namespace std;
//单例模式:懒汉式
class Singleton{
public:
//3)使用静态成员函数获取单例对象
static Singleton& getInstance(void){
//一个线程中加锁成功后,必须等它解锁了,其他的线程才能访问
pthread_mutex_lock(&mutex);//加锁
if(s_instance == NULL){
s_instance = new Singleton(123);
}
++s_count;//s_count = s_count + 1
pthread_mutex_unlock(&mutex);//解锁
return *s_instance;
}
//单例对象不用即销毁,销毁时机?
//所有的使用者都不再使用,才能销毁
void release(void){
pthread_mutex_lock(&mutex);//加锁
if(--s_count == 0){
delete s_instance;
s_instance = NULL;
}
pthread_mutex_unlock(&mutex);//解锁
}
void print(void) const{
cout << m_data << endl;
}
private:
//1)私有化构造函数
Singleton(int data):m_data(data){
cout << "单例对象被创建了" << endl;
}
//将缺省的拷贝构造函数声明为私有
Singleton(const Singleton&);
//析构函数
~Singleton(void){
cout << "单例对象被销毁了" << endl;
}
//2)使用静态成员变量来维护唯一的对象
static Singleton* s_instance;//声明
//计数:记录单例对象使用者的个数
static int s_count;
//互斥锁(实现多线程之间的互斥访问)
static pthread_mutex_t mutex;
private:
int m_data;
};
Singleton* Singleton::s_instance = NULL;
int Singleton::s_count = 0;
//定义和初始化互斥锁
pthread_mutex_t Singleton::mutex
= PTHREAD_MUTEX_INITIALIZER;
int main(void){
//Singleton s(123);
//Singleton* ps = new Singleton(123);
cout << "main开始运行" << endl;
//++s_count:1,new
Singleton& s1 = Singleton::getInstance();
//++s_count:2
Singleton& s2 = Singleton::getInstance();
//++s_count:3
Singleton& s3 = Singleton::getInstance();
s1.print();
s1.release();//--s_count:2
s2.print();
s3.print();
cout << "&s1=" << &s1 << endl;
cout << "&s2=" << &s2 << endl;
cout << "&s3=" << &s3 << endl;
s2.release();//--s_count:1
s3.release();//--s_count:0,delete
return 0;
}
十九.成员指针(了解)
1.成员变量指针
①定义
类型 类名::*成员指针变量名 = &类名::成员变量;
②使用
对象.*成员指针变量名;
注:".*"被称为直接成员指针解引用操作符
对象指针->*成员指针变量名;
注:"->*"被称为间接成员指针解引用操作符
eg.
#include <iostream>
#include <cstdio>
using namespace std;
class Student{
public:
Student(const string& name):m_name(name){}
int m_age;
string m_name;
};
int main(void){
//成员变量指针
string Student::*pname = &Student::m_name;
Student s("张飞");
Student* ps = new Student("赵云");
cout << s.*pname << endl;
cout << ps->*pname << endl;
//成员变量地址=对象地址+成员变量指针相对地址
printf("pname=%p\n",pname);
printf("&s=%p\n",&s);
printf("&s.m_name=%p\n",&s.m_name);
return 0;
}
2.成员函数指针
①定义
返回类型 (类名::*成员函数指针)(参数表)=&类名::成员函数;
②使用
(对象.*成员函数指针)(实参表);
(对象指针->*成员函数指针)(实参表);
eg.
#include <iostream>
using namespace std;
class A{
public:
void func1(void){
cout << "A::func1" << endl;
}
void func2(void){
cout << "A::func2" << endl;
}
};
int main(void){
void (A::*pfunc)(void) = &A::func1;
A a; //栈对象
A* pa = new A; //堆对象
(a.*pfunc)();
(pa->*pfunc)();
//该语法的指针是可以修改指向的
pfunc = &A::func2;
(a.*pfunc)();
(pa->*pfunc)();
return 0;
}
二十.操作符重载(operator)
1.基本概念
操作符重载就是一些具有特殊名称的函数,“operator操作符”,把已存在操作进行重新定义,针对自定义的类类型对象,完成自定义的运算功能。
eg.
int a=10,b=20;
a+b;
---------------
Integer a(10);
Integer b(20);
a+b; //operator+
eg.复数(x+yi)
1+2i + 3+4i = 4+6i
eg.
#include <iostream>
using namespace std;
class Complex{
public:
Complex(int r,int i):m_r(r),m_i(i){}
void print(void) const {
cout << m_r << "+" << m_i << 'i' << endl;
}
//c1+c2 ==> c1.operator+(c2)
const Complex operator+(
const Complex& c) const {
Complex res(m_r+c.m_r,m_i+c.m_i); //构造一个返回结果的实例对象
return res;
}
private:
int m_r;//实部
int m_i;//虚部
};
int main(void){
Complex c1(1,2);
Complex c2(3,4);
c1.print();
c2.print();
//Complex c3 = c1.operator+(c2)
Complex c3 = c1 + c2;
c3.print();//4+6i
return 0;
}
//a=b ==> a.operator=(b)
//a+b ==> a.operator+(b)