目录
一,this指针
1.1什么是this指针?
C++编译器给每个“非静态成员函数”怎加了一个隐藏的指针参数,让该指针指向当前对象,并且在函数运行时调用该对象,在函数体中,所有的“成员变量”都是通过指针去访问。
1.2this指针的特性
1,this指针的类型,类类型*const,this指针的值固定,所以在成员函数中无法给this指针赋值。
2,只能在成员函数内部使用。
3,this指针本质上是“成员函数”的形参,调用成员函数时,将对象地址传给this,所以对象中不存储this指针。
4,this指针是成员函数中一个隐藏的指针形参,一般编译器自动生成传递,所以用户不能创建或者传递。
二,深浅拷贝
2.1什么是浅拷贝
浅拷贝,也叫值拷贝,位拷贝,编译器只是将对象中的值拷贝过来。
class Date
{
public:
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._month;
}
private:
int _year;
int _month;
int _day;
};
2.2什么是深拷贝
假如一个家庭有两个孩子,两个孩子一起玩一个玩具,就叫做浅拷贝,两个对象共用同一资源。
深拷贝就是两个孩子各自有自己的玩具,每个对象有自己独有的资源,互不干扰。
2.3为什么需要深拷贝
如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会像该资源释放掉,而另一对象不知道该资源已经释放,所以当其他对象访问该资源是就会发现访问违规。
看如下代码,以String为例,先实现浅拷贝,打印析构次数,观察结果
class String
{
public:
String(const char* str = "")
{
assert(str);
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
cout << "~String()" << endl;
}
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2(s1);
return 0;
}
可以发现,明明是两个对象,但是却只打印了一次String,而且空了一行,其实只要我们看到空了一行,就代表程序崩溃了,s2析构释放了数据,而s1析构释放时等待它的只是一个野指针,所以崩溃了。
2.4实现深拷贝
class String
{
public:
String(const char* str = "")
{
assert(str);
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
:_str(nullptr)
{
String tmp(s._str);
std::swap(_str, tmp._str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
cout << "~String()" << endl;
}
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2(s1);
return 0;
}
三,初始化列表
3.1什么是初始化列表
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟以一个放在括号中的初始值或表达式。
Date(int year = 1970, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
注意
1,每个成员变量在初始化列表中只能出现一次
2,类包含引用成员变量,const成员变量和无默认构造函数的自定义成员时
尽量使用初始化列表,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会使用初始化列表初始化。
class Time
{
public:
Time(int hour = 0)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int day)
{}
private:
int _day;
Time _t;
};
int main()
{
Date(1);
return 0;
}
3.2初始化列表的初始化顺序
成员变量在初始化列表初始化顺序是类中的声明顺序,与其在初始化列表中的先后次序无关
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)//虽然列表中先初始的_a1,但因为_a2先声明的,所以先初始的_a2
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;//先声明_a2,再声明的_a1
};
int main()
{
A aa(1);
aa.Print();
}
四,赋值运算符重载
赋值运算符重载格式
①参数类型:const T&,传递引用可以提高效率。(这里的T是模板)
②返回值类型:T&,返回引用可以提高返回的效率,有返回值是为了支持连续赋值。
③返回*this:要符合连续赋值的含义。
class Date
{
public:
Date(int year = 1970, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)//传递引用
{//返回引用
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;//返回*this
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 9, 14);
Date d2 = d1;
d2.Print();
return 0;
}
赋值运算符只能重载成类的成员函数不能重载成全局函数,因为如果不在类里面显示实现,编译器会生成一个默认的,此时用户再在类外面自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,所以赋值运算符重载只能是类的成员函数。
内置类型直接赋值,而自定义类型成员变量调用自己的赋值运算符重载完成赋值。
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time& operator=(const Time& t)
{
if (this != &t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
}
cout << "Time operator=()" << endl;
return *this;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
//内置类型
int _year = 1970;
int _month = 1;
int _day = 1;
Time _t;//自定义类型
};
int main()
{
Date d1;
Date d2;
d1 = d2;
return 0;
}
注意:如果类中未涉及到资源管理,赋值运算符是否实现 都可以,一旦涉及到资源管理必须要实现,这部分咱们在STL中再详细讲解。
五,取地址以及const取地址操作符重载
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
这两个运算符一般使用编译器默认生成的取地址重载即可。
六,static成员
用static修饰成员变量,称之为静态成员变量;用static修饰成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
下面的代码可以计算一个程序中创建了多少个类对象。
特性:
①静态成员为所有类共享,不属于某个具体的对象,存放在静态区。
②静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。
③静态成员函数没有this指针,不能访问任何非静态成员。
④类中的静态成员也受public,private,protected的限制。
class A
{
public:
A()
{
++_count;
}
A(const A& t)
{
++_count;
}
~A()
{
--_count;
}
static int GetACount()
{
return _count;
}
private:
static int _count;
};
int A::_count = 0;
int main()
{
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
return 0;
}
七,友元
友元是一种突破封装的方式,有时能提供便利。但友元破坏了封装,不宜多用。
7.1友元函数
问题:现在我们尝试去重载operator<<,然后发现operator<<无法重载为成员函数,因为cout<<的输出流对象和this指针抢占了<<左边的位置,所以需要重载成全局函数,但是这样就无法访问成员,所以用友元来解决。
特性:
①友元函数可以访问类的私有和保护成员,但不是类的成员函数
②友元函数不能用const修饰
③友元函数不受访问限定符限制
④一个函数可以是多个类的友元函数
⑤友元函数的调用与普通函数的调用原理相同
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);//声明为友元函数
friend istream& operator>>(istream& _cin, const Date& d);
public:
Date(int year=1970,int month=1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
istream& operator>>(istream& _cin, const Date& d)
{
_cin >> d._year;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
int main()
{
Date d;
cin >> d;
cout << d << endl;
return 0;
}
7.2友元类
友元类的所有成员函数都可以时零另一个类的友元函数,都可以访问另一个类的非公有成员。
注意:
①友元关系是单向的,比如B在A中声明是A的友元类,B可以访问A的非公有成员,但是A不能访问B的非公有成员。
②友元关系不能传递,C是B的友元,B是A的友元,但是不能代表C是A的友元。
class Time
{
friend class Date;
public:
Time(int hour = 0,int minute = 0, int second = 0)
:_hour(hour)
,_minute(minute)
,_second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1970, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
void SetTimeClass(int hour, int minute, int second)
{
//直接访问Time类的私有
_t._hour = hour;
_t._minute = minute;
_t._second = second;
cout << _year << "-" << _month << "-" << _day << "-" << _t._hour << "-" << _t._minute << "-" << _t._second << endl;
}
private:
int _year;
int _month;
int _day;
Time _t;//用Time类声明一个对象,Date可以访问该对象的私有
};
int main()
{
Date d(2023,9,14);
d.SetTimeClass(20, 2, 14);
return 0;
}
八,匿名对象
class A
{
public:
A(int a=0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A();
//实例化时不接对象名,所以匿名对象的生命周期只有这一行
//所以在这一行调用构造函数后,直接调用了析构函数
//现在这部分不做特别难的要求,等到后面map会用到,到时候会详细讲解
return 0;
}