->
浅拷贝带来的问题是堆区的内存重复释放
->
成员属性设置为私有
可以自己控制操作属性的权限
->
构造函数
可以有参数可以重载
没有返回值也不用写 void
类名(){}
创建对象的时候系统自动调用且只调用一次
有参构造、无参构造
拷贝构造函数
析构函数
不能有参数,不发生重载
~类名(){}
对象在销毁前会自动调用析构且只调用一次
->
如果用户定义了有参构造,c++不再提供无参构造,还是会提供拷贝构造
如果用户定义了拷贝构造,c++不提供其他构造函数
->
浅拷贝是简单的赋值
c++默认的拷贝构造函数默认是浅拷贝
深拷贝再堆区重新申请空间,进行拷贝操作
->
c++类中的成员可以是另一个类的对象
称为 对象成员
class A{}
class B
{
A a;
}
构造的时候先A后B
"先有砖头后有房子"
"先造零件后装机"
析构的时候先B后A
"拆房子,房子先没,砖后没"
"先拆机后扔零件"
->
静态成员
所有对象共享同一份数据
编译阶段分配内存
类内声明、类外初始化
静态成员函数
所有对象共享同一个函数
静态成员函数智能访问静态成员变量
静态成员变量不属于某个对象,所有对象共享同一份数据
静态成员变量有两种访问方式
1.通过对象访问 Person p1; p1.m_a;
2.通过类名访问 Person::m_a
静态成员函数也是同样的两种访问方式
->
对象模型和this指针
this指针的本质是一个指针常量,指向不可修改,指向的值可以修改(非const变量)
c++中:
类内的成员和成员函数分开存储
只有非静态成员变量才属于类的对象上
this指针指向被调用的成员函数所属的对象
this是隐含每一个非静态成员函数内的一种指针
当形参和成员变量同名的时候,可以用this指针来区分
在类的非静态成员函数中返回对象本身,return *this
->
空对象占用内存空间为 1
c++编译器会给每个空对象也分配 1 字节的空间,是为了区分对象占内存的位置
每个空对象也应该有一个独一无二的内存地址
->
Person& PersonAddAge(const Person &p)
{
this->age+=p.age;
cout<<this->age<<endl;
// 返回本体
return *this;
}
Person p1(10);
Person p2(10);
p1.PersonAddAge(p2).PersonAddAge(p2).PersonAddAge(p2);
如果函数返回的不是Person的引用,能进行加法,但p1.age还是20,因为会调用拷贝构造函数
返回一个和p1一样的对象但不是p1
不加引用可以:
p3=p1.PersonAddAge(p2).PersonAddAge(p2).PersonAddAge(p2);
这样p3.age=40
->
空指针调用成员函数
class Person
{
public:
void showClassName()
{
cout<<"this is Person class."<<endl;
}
void showPersonAge()
{
cout<<"age = "<<m_age<<endl;
}
int m_age;
};
Person *p=NULL;
p->showClassName(); //不会报错,因为函数内部没有访问类的属性
p->showPersonAge(); //报错
void showPersonAge()
{
cout<<"age = "<<m_age<<endl;
//相当于cout<<"age = "<<this->m_age<<endl;
因为p是一个空指针,所以会报错
}
if (this==NULL)
{
return;
}
加上一个判断防止代码崩溃
->
常函数:
成员函数后加const后我们称为这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数中依然可以修改
void showPerson() const //相当于 const Person *this;
{
this->m_age=100; //报错,加了const,值不可修改
}
常对象:
声明对象前加const称该对象为常对象
常对象只能调用常函数
const Person p;
->
友元
在类中声明一个函数,前面加上friend关键字
那么这个函数也能访问类中private中的私有成员
全局函数做友元:
class Buliding
{
friend void goodGay(Buliding &building);
public:
string m_SittingRoom;
private:
string m_BedRoom;
public:
Buliding()
{
this->m_BedRoom="BedRoom";
this->m_SittingRoom="SittingRoom";
}
};
void goodGay(Buliding &building)
{
cout<<"calling "<<building.m_SittingRoom<<endl;
cout<<"calling "<<building.m_BedRoom<<endl;
}
成员函数做友元:
一个类的成员函数可以访问另一个类的私有成员
在类中加上
friend void 类名::函数名;
类做友元:
一个类能访问另一个类的私有成员
在类中加上
friend class 类名;
就可以了
->
运算符重载
实现自定义数据类型的运算
Person PersonAddAge(Person &p)
{
Person tmp;
tmp.m_a=this->m_a+p.m_a;
return tmp;
}
p3=p1.PersonAddAge(p2)
运算符重载
成员函数重载:
Person operator+ (Person &p)
{
Person tmp;
tmp.m_a=this->m_a+p.m_a;
return tmp;
}
可以简化为
p3=p1+p2
也可以
p3=p1.operator+(p2)
全局函数重载:
Person operator+ (Person &p1,Person &p2)
{
Person tmp;
tmp.m_a=this->m_a+p.m_a;
return tmp;
}
->
左移运算符重载 <<
cout<<a<<endl;
现在有一个Person p;
想要直接 cout<<p<<endl;
就可以输出p的所有属性: 重载左移运算符
如果用成员函数重载:
void operator<<(cout){} 相当于 p.operator<<(cout) -> p<<cout
结果显然不对
所以重载左移运算符不会用成员函数重载,因为无法实现cout在左侧
只能利用全局函数重载
void operator<<(ostream &cout,Person &p) //本质 operator<<(cout,p) -> cout<<p
{
//参数改为out,a,c都没问题,因为ostream对象只能有一个
cout<<"m_a = "<<p.m_a<<" m_b = "<<p.m_b<<endl;
}
cout<<p;
cout后面可以跟很多个<<,链式一直进行下去
所以函数的返回值要是cout本身,ostream & operator<<(ostream &cout,Person &p);
因为一般把类的属性设置为private,所以<<运算符重载可以设置为友元
->递增运算符++
++可以在左边也可以在右边
++a a++
两种都可以重载
++a: 前置递增
MyInteger & operator++()
{
this->m_num++;
return *this;
}
a++: 后置递增
注意不返回引用,因为temp是局部变量
函数执行完就被释放了,return之后就没了,后面都是非法操作
MyInteger operator++(int)
{
//先记录当前的值
MyInteger tmp=*this;
//后递增
this->m_num++;
//返回记录结果(值)
return temp;
}
在重载的函数体内部不需要区分前置后置,this->m_num++ / ++this->m_num 都可以
->
重载赋值运算符 =
重载赋值运算符可以避免 p2=p1 带来的浅拷贝问题
考虑到a=b=c语法的存在
所以返回值也是对象自身Person &
Person& operator=(Person &p);
->
重载 ==
a==b 看形式相当于a调用==,所以函数参数是b
显然关系函数的返回值类型是bool
所以:
bool operator==(Person &p);
函数体内部自定义判断的规则
其他的关系运算符类似
->
函数调用运算符重载 () 小括号
由于重载后的使用方式非常像函数的调用,因此称为 仿函数
仿函数没有固定写法,非常灵活
在一个类中重载小括号
class MyPrint
{
public:
void operator()(string text);
};
void MyPrint::operator()(string text)
{
cout<<text<<endl;
}
void test01()
{
MyPrint myprint;
myprint("hello world");
}
类名后面加小括号直接调用
仿函数非常灵活没有固定的写法,参数也可以很多个
使用的时候也不需要创建实例对象
可以MyPrint()("hello world"); // 匿名对象,执行完了这一行就会被释放
|