C++学习笔记(六)

十七.拷贝构造和拷贝赋值

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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

boss-dog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值