目录五
十五.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;