C++学习笔记(五)

十五.this指针和常成员函数

1.this指针

#include <iostream>
#include <cstring>
using namespace std;
class Dummy{
public:
    Dummy(const char* str)
        //:m_str(str),m_len(m_str.size()){}
        //:m_str(str),m_len(strlen(str)){}
        :m_str(str?str:""),
         m_len(strlen(str?str:"")){}
    size_t m_len;
    string m_str;
};
int main(void){
    //Dummy d("hello");
    Dummy d(NULL);
    cout << d.m_str << "," << d.m_len << endl;

    return 0;
}

①类中的成员函数(包括构造函数、析构函数)中都隐藏一个该类类型指针参数,名为this;在成员函数中访问类中的其它成员,其本质都是this来实现的。
②对于普通成员函数,this指向调用对象的地址;如果对于构造函数,this指向正在创建的对象地址。
③大多数情况可以忽略this,在成员函数宏直接访问类中的其它成员,但是以下几个特殊场景必须要使用this指针:
–》区分作用域 eg.this->m_name = m_name;
eg.

#include <iostream>
using namespace std;
class Teacher{
public:
    /*
    Teacher(const string&name,int age,double sal)
        :m_name(name),m_age(age),m_sal(sal){
        cout << "构造函数:" << this << endl;    
    }*/
    //当参数变量和成员变量名字相同时,可以通过
    //this区分,通过this所访问到的一定是成员变量
    Teacher(const string& m_name,int m_age,
            double m_sal){
        this->m_name = m_name;
        this->m_age = m_age;
        this->m_sal = m_sal;
    }
    void print(void){
        cout << m_name << "," << m_age << "," << m_sal << endl;
        cout << this->m_name << ","<< this->m_age << "," << this->m_sal << endl;
    }/*编译器处理后:
    void print(Teacher* this){
        cout << this->m_name << ","<< this->m_age << "," << this->m_sal << endl;
    }*/
private:
    string m_name;
    int m_age;
    double m_sal;
};
int main(void){
    Teacher t1("小明",45,8000);
    Teacher t2("小张",46,9000);
    t1.print();//Teacher::print(&t1)
    t2.print();//Teacher::print(&t2)

    cout << "&t1=" << &t1 << endl;
    cout << "&t2=" << &t2 << endl;

    return 0;
}

–》从成员函数返回调用对象自身(返回自引用)(重点掌握)
eg.

#include <iostream>
using namespace std;
class Counter{
public:
    Counter(int count=0):m_count(count){}
    Counter& add(void){
        ++m_count;
        //this指向调用对象,*this就是调用对象自身
        return *this;//返回自引用
    }
    void print(void){
        cout << "计数值:" << m_count << endl;
    }
private:
    int m_count;
};
int main(void){
    Counter c;
    c.add().add().add();
    c.print();//3
    return 0;
}

–》从类的内部销毁对象自身(对象自销毁)
eg.

#include <iostream>
using namespace std;
class Counter{
public:
    Counter(int count=0):m_count(count){}
    Counter& add(void){
        ++m_count;
        //this指向调用对象,*this就是调用对象自身
        return *this;//返回自引用
    }
    void print(void){
        cout << "计数值:" << m_count << endl;
    }
    void destroy(void){
        cout << "this:" << this << endl;
        delete this;//对象自销毁
    }
private:
    int m_count;
};
int main(void){
    Counter* pc = new Counter;
    pc->add();
    pc->print();//1
    //delete pc;
    cout << "pc:" << pc << endl;
    pc->destroy();
    return 0;
}

–》作为成员函数的实参,实现不同对象之间的交互(了解),这种代码可读性差
eg.

#include <iostream>
using namespace std;
class Student;//短视声明
class Teacher{
public:
    void educate(Student* s);
    void reply(const string& answer);
private:
    string m_answer;
};
class Student{
public:
    void ask(const string& q,Teacher* t);
};
void Teacher::educate(Student* s){
    //通过this指针,将教师对象地址传给学生对象
    s->ask("什么是this指针?",this);//1)
    cout << "学生回答:" << m_answer << endl;//5)
}
void Teacher::reply(const string& answer){
    m_answer = answer;//4)
}
void Student::ask(const string& q,Teacher* t){
    cout << "问题:" << q << endl;//2)
    t->reply("this就是指向调用对象的地址");//3)
}
int main(void){
    Teacher t;
    Student s;
    t.educate(&s);
    return 0;
}

2.常成员函数

①在一个成员函数参数表的后面加const,这个成员函数就是常成员函数(常函数)。

返回类型 函数名(形参表) const {函数体}

eg.

#include <iostream>
using namespace std;
class A {
public:
    A(int data = 0) :m_data(data) {}
    //void print(const A* this)
    void print(void) const {//常成员函数
        //cout << this->m_data++ << endl;
        cout << m_data/*++*/ << endl; //这里使用m_data++是错误的,因为加了const

        //const_cast<A*>(this)->m_spec = 200;
        m_spec = 200;
        cout << m_spec << endl;
    }
private:
    int m_data;
    mutable int m_spec;
};
int main(void) {
    A a(100);
    a.print();//100
    a.print();//若const,则输出m_data++,结果为101
    return 0;
}

②常成员函数中this指针是一个常指针,不能在常成员函数中直接修改成员变量的值。
(注:被mutable修饰的成员变量,可以在常成员函数直接修改)
③非常对象既可以调用非常函数也可以调用常函数;但是常对象只能调用常函数,不能调用非常函数(重点掌握)
(注:常对象也包括常指针和常引用)
eg.

#include <iostream>
using namespace std;
class A{
public:
    //void func1(const A* this)
    void func1(void) const {
        cout << "常函数" << endl;
        //func2();//this->func2()
    }
    //void func2(A* this)
    void func2(void){
        cout << "非常函数" << endl;
    }
};
int main(void){
    A a;
    a.func1();//A::func1(&a),A*
    a.func2();//A::func2(&a),A*
    const A a2 = a;
    a2.func1();//A::func1(&a2),const A*
    //a2.func2();//A::func2(&a2),const A*

    const A* pa = &a;//pa:常指针
    pa->func1();
    //pa->func2();
    
    const A& ra = a;//ra:常引用
    ra.func1();
    //ra.func2();

    return 0;
}

④同一个类中,函数名和参数表相同的成员函数,其常版本和非常版本可以构成有效的重载关系;常对象匹配常版本,非常对象匹配非常版本。
eg.

#include <iostream>
using namespace std;
class A{
public:
    void func(void)const{
        cout << "func常版本" << endl;    
    }
    void func(void){
        cout << "func非 常版本" << endl;
    }
};
int main(void){
    A a;
    a.func();//非 常版本
    const A a2 = a;
    a2.func();//常版本
    return 0;
}

十六.析构函数(Destructor)

1.语法

class 类名{
		~类名(void){
			//主要负责清理对象生命周期中的动态资源
		}
};

①函数名一定是"~类名"
②没有返回类型,也没有参数
③不能被重载,即一个类只能有一个析构函数

2.当对象被销毁时,析构函数将自动被调用

①栈对象离开所在作用域时,析构函数被作用域终止的右花括号( } )调用。
②堆对象的析构函数被delete操作符调用。
(注:delete对象时,会自动调用析构函数,在释放对象自身的内存,而如果是free函数只会释放自身内存不会调用析构函数)
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 << "析构函数" << endl;
        delete m_pi;
    }
private:
    int* m_pi;
};
int main(void){
    if(1){
        Integer i(100);
        i.print();//100
        cout << "test1" << endl;
        Integer* pi = new Integer(200);
        pi->print();//200
        delete pi;//->析构函数
        cout << "test3" << endl;
    }//->析构函数
    cout << "test2" << endl;
    return 0;
}

3.如果一个类自己没有定义析构函数,那么编译器将会为该类提供一个缺省的析构函数

①对于基本类型的成员变量,什么也不做。
②对于类类型的成员变量(成员子对象),将会自动调用相应类的析构函数。

4.对象创建和销毁的过程(重要)

①创建
–》分配内存
–》构造成员子对象(按声明的顺序)
–》执行构造函数代码
②销毁
–》执行析构函数代码
–》析构成员子对象(按声明的逆序)
–》释放内存

eg.

#include <iostream>
using namespace std;
class A{
public:
    A(void){
        cout << "A的构造函数" << endl;
    }
    ~A(void){
        cout << "A的析构函数" << endl;
    }
};
class B{
public:
    B(void){
        cout << "B的构造函数" << endl;
    }
    ~B(void){
        cout << "B的析构函数" << endl;
    }
    A m_a;//成员子对象
};
int main(void){
    B b;
    return 0;
}

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

1.浅拷贝和深拷贝

①如果类中包含了指针形式的成员变量,缺省的拷贝构造只是复制了指针变量自身,而没有复制指针所指向的内容,这种拷贝方式被称为浅拷贝
②浅拷贝将会导致不同对象之间的数据共享,如果数据在堆区,析构时还可能会引发"double free",导致进程终止,所以就必须自定义一个指针复制指针所指向内容的拷贝构造函数,即深拷贝
在这里插入图片描述
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;
    }
private:
    int* m_pi;
};
int main(void){
    Integer i1(100);
    Integer i2(i1);//拷贝构造
    i1.print();//100
    i2.print();//100
    return 0;
}

练习

①实现字符串类(String),里面包括构造函数、析构函数、拷贝构造函数。

class String{
public:
		//构造函数:支持const char*字符串
		String(const char* str = NULL){}
		//析构函数
		~String(void){}
		//拷贝构造函数
		String(const String& that){}
		//打印
		void print(void)const{
			cout << m_str << endl;
		}
private:
		char* m_str;
};

eg.

#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类型
	}

	//打印函数
	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();
	return 0;
}

#include <iostream>
using namespace std;
class B{
private:
    int data;
public:
    B(){
        cout << "defalt constructor" << endl;
    }
    ~B(){
        cout << "destructor" << endl;
    }
    B(int i):data(i){
        cout << "constructed by parameter:" 
            << data << endl;
    }
    B(const B& that){
        cout << "copy constructor" << endl;
    }
};
B Play(B b){
    return b;
}
int main(int argc,char** argv){
    B temp = Play(5);
    return 0;
}
//问题1:程序结果是什么?为什么是这个结果
//答:constructed by parameter:5
//    copy constructor
//    destructor
//    destructor
//问题2:B(int i):data(i),这种语法专业术语叫什么? 
//答:初始化列表
//问题3:Play(5),形参类型是类,而5是个常量,这样写合法么?为什么?
//答:合法,意思是先把5作为b对象,即相当于B b=5;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

boss-dog

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

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

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

打赏作者

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

抵扣说明:

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

余额充值