文章内容是把黑马的课堂笔记选择性的复制过来,便于后期学习,非原创!
封装、对象的初始化和清理
1、构造函数和析构函数
构造函数和析构函数会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。编译器提供的构造函数和析构函数是空实现。
2、C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
class Person {
public:
Person() {
cout << "无参构造函数!" << endl;
mAge = 0;
}
Person(int age) {
cout << "有参构造函数!" << endl;
mAge = age;
}
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
mAge = p.mAge;
}
//析构函数在释放内存之前调用
~Person() {
cout << "析构函数!" << endl;
}
public:
int mAge;
};
//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
Person man(100); //p对象已经创建完毕
Person newman(man); //调用拷贝构造函数
Person newman2 = man; //拷贝构造
//Person newman3;
//newman3 = man; //不是调用拷贝构造函数,赋值操作
}
//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02() {
Person p; //无参构造函数
doWork(p);//值传递时就是产生一个拷贝 一个副本 实参传给形参时调用拷贝构造函数,改调用后的值不改变原来的值
}
//3. 以值方式返回局部对象
Person doWork2()
{
Person p1;
cout << (int *)&p1 << endl;//看看对象的地址
return p1;//return局部对象值返回时是拷贝了一个新的对象来返回
}
void test03()
{
Person p = doWork2();
cout << (int *)&p << endl;//拷贝的对象的地址
}
int main() {
//test01();
//test02();
test03();
system("pause");
return 0;
}
3、默认情况下,c++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)(空实现)
- 默认析构函数(无参,函数体为空)(空实现)
- 默认拷贝构造函数,对属性进行值拷贝(值拷贝)(浅拷贝)
构造函数调用规则如下:
- 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
如果提供有参,左边
如果提供拷贝,右边
4、深浅拷贝
浅拷贝:简单的赋值拷贝操作 如拷贝构造函数
深拷贝:在堆区重新申请空间,进行拷贝操作
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int age ,int height) {
cout << "有参构造函数!" << endl;
m_age = age;
m_height = new int(height);//堆区开辟的数据由程序员手动开辟 手动释放,对象销毁前把堆区释放
}
//拷贝构造函数
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;//编译器提供:m_age = p.m_age;m_height = p.m_height;
m_height = new int(*p.m_height);//用深拷贝,创建新内存
}
//析构函数
~Person() {
cout << "析构函数!" << endl;//将堆区数据释放,p1 p2都会执行析构,规则是先进后出,p2先被释放,然后在释放p1 堆区内存被重复释放
if (m_height != NULL)
{
delete m_height;
m_height = NULL;//防止野指针出现
}
}
public:
int m_age;
int* m_height;//为指针
};
void test01()
{
Person p1(18, 180);
Person p2(p1);//把m_height开辟的地址逐字节拷贝
cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;
cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}
int main() {
test01();
system("pause");
return 0;
}
5、类对象作类成员
- 当类中成员是其他类对象时,我们称该成员为 对象成员
构造的顺序是 :先调用对象成员的构造,再调用本类构造
析构顺序与构造相反
6、静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
- 静态成员变量
- 所有对象共享同一份数据,不属于某一个对象
- 在编译阶段分配内存//全局区
- 类内声明,类外初始化
静态成员变量也是有访问权限的
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
class Person
{
public:
static int m_A; //静态成员变量 类内声明
int m_C; // 非静态成员变量
static void func()
{
cout << "func调用" << endl;
m_A = 100;//静态成员函数可以访问静态成员变量
//m_C = 100; //错误,不可以访问非静态成员变量,因为每个对象都有一份m_c非静态成员变量,而静态成员函数只有一份,没有体现属于哪个对象,所以不知道要修改哪个。比如当用类调用时,person::func(),不知道是哪个。
}
private:
static int m_B; //静态成员变量也是有访问权限的,类内声明
static void func2()//静态成员函数也是有访问权限的
{
cout << "func2调用" << endl;
}
};
int Person::m_A = 10;//成员变量类外初始化
int Person::m_B = 10;
void test01()
{
//静态成员变量、成员函数两种访问方式
//1、通过对象
Person p1;
p1.m_A = 100;//
cout << "p1.m_A = " << p1.m_A << endl;
p1.func();
Person p2;
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据,都会变成200
cout << "p2.m_A = " << p2.m_A << endl;
//2、通过类名
cout << "m_A = " << Person::m_A << endl;//因为不属于任意一个对象,person作用域下
//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}
Person::func();
//Person::func2(); //私有权限访问不到
int main() {
test01();
system("pause");
return 0;
}
C++对象模型和this指针
7、成员变量和成员函数分开存储
- 在C++中,类内的成员变量和成员函数分开存储,虽然作为一个整体表现事物进行封装
- 只有非静态成员变量才属于类的对象上(静态成员对象、静态成员函数、非静态成员函数都不是,都只有一份)
class Person {
public:
Person() {
mA = 0;
}
//非静态成员变量占对象空间,按int分,4个字节
int mA;
//静态成员变量不占对象空间
static int mB;
//函数也不占对象空间,所有函数共享一个函数实例
void func() {//非静态成员函数只有一份,通过this来区分谁在调用。
cout << "mA:" << this->mA << endl;
}
//静态成员函数也不占对象空间
static void sfunc() {
}
};
int person::mb=10;不占对象空间
int main() {
//person p;
//cout<<sizeof(p)<<endl;//4字节
cout << sizeof(Person) << endl;//4字节
system("pause");
return 0;
}
8、this指针
- 每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
- c++通过提供特殊的对象指针,this指针,解决区分哪个对象调用自己的问题。this指针指向被调用的成员函数所属的对象
- this指针是隐含每一个非静态成员函数内的一种指针
- this指针不需要定义,直接使用即可
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分,this指针指向的是被调用的成员函数所属对象。即p1、p2等
this->age = age;//如果不带this 两边都是形参age age=age,要么加m_,要么this指针
}
Person& PersonAddPerson(Person p)//以引用的方式返回,如果不是引用,是值传递,
//结果是20,因为调用拷贝构造函数,复制一份,p2‘,假如是值传递,拷贝出另一个,创建出一个新的对象,
//p2本身没有继续相加,所以要用引用的方式返回
{
this->age += p.age;
//返回对象本身
return *this;
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10);
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);//链式编程思想
cout << "p2.age = " << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
9、空指针
- C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
- 如果用到this指针,需要加以判断保证代码的健壮性
//空指针访问成员函数
class Person {
public:
void ShowClassName() {
cout << "我是Person类!" << endl;
}
void ShowPerson() {
if (this == NULL) {
return;
}
cout << mAge << endl;//默认都是this->mage,但由于Person * p = NULL;,所以this是空指针,没有对象,所以无法访问年龄
}
public:
int mAge;//非静态成员变量
};
void test01()
{
Person * p = NULL;
p->ShowClassName(); //空指针,可以调用成员函数,注意指针调用函数或访问属性时用箭头
p->ShowPerson(); //但是如果成员函数中用到了this指针,就不可以了
}
int main() {
test01();
system("pause");
return 0;
}
10、常函数和常对象
常函数:
- 成员函数后加const后我们称为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数,都只读
- 常对象不能修改成员变量的值,但是可以访问
常对象可以修改mutable修饰成员变量
this指针的本质是一个指针常量
class Person {
public:
Person() {
m_A = 0;
m_B = 0;
}
//this指针的本质是一个指针常量,指针的指向不可修改
//如果想让指针指向的值也不可以修改,需要声明常函数
void ShowPerson() const {
//const Type* const pointer;
//this = NULL; //函数一旦被调用,this指针指向就确定了,不能修改指针的指向 Person* const this;
//this->mA = 100; //ma=100;等价于this->mA = 100;。this指针本质是指针常量,指针指向不可修改,但是this指针指向的对象的数据是可以修改的
//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
this->m_B = 100;
}
void MyFunc() const {
//mA = 10000;//如果没有const,内部能够修改属性,那么常对象是不能修改属性的,所以矛盾了
}
public:
int m_A;
mutable int m_B; //特殊变量,即使在常函数中也可以修改,常对象也可以修改,可修改 可变的
};
//const修饰对象 常对象
void test01() {
const Person person; //常量对象
cout << person.m_A << endl;
//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
person.m_B = 100; //但是常对象可以修改mutable修饰成员变量
//常对象访问成员函数
person.MyFunc(); //常对象不能调用普通成员函数,因为普通成员函数可以修改属性,常对象只能调用常函数
}
int main() {
test01();
system("pause");
return 0;
}
友元
11、友元
- 友元的目的就是让一个函数或者类 访问另一个类中私有成员
- 友元的关键字为 friend
- 友元的三种实现
- 全局函数做友元
- 类做友元
- 成员函数做友元
全局函数做友元
class Building
{
//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容
friend void goodGay(Building * building);
public:
Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom; //卧室
};
void goodGay(Building * building)
{
cout << "好基友正在访问: " << building->m_SittingRoom << endl;
cout << "好基友正在访问: " << building->m_BedRoom << endl;
}
void test01()
{
Building b;
goodGay(&b);
}
int main(){
test01();
system("pause");
return 0;
}
类做友元
class Building;//声明
class goodGay
{
public:
goodGay();
void visit();
private:
Building *building;
};
class Building
{
//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
friend class goodGay;
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom;//卧室
};
Building::Building()//类外写成员函数
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
goodGay::goodGay()
{
building = new Building;//在堆区创建一个对象,并且让building*building指向新对象,返回的是地址。
}
void goodGay::visit()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void test01()
{
goodGay gg;//创建goodgay对象,构造函数先会创建一个building,会调用building构造函数,会给building对象赋初值。
gg.visit();//调用时就能访问,但bedroom是私有,用friend。
}
int main(){
test01();
system("pause");
return 0;
}
成员函数做友元
class Building;
class goodGay
{
public:
goodGay();
void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容
void visit2();
private:
Building *building;
};
class Building
{
//告诉编译器 goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容
friend void goodGay::visit();
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom;//卧室
};
Building::Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
goodGay::goodGay()
{
building = new Building;
}
void goodGay::visit()//必须定义在被设置为友元的类的后面。
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void goodGay::visit2()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
//cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void test01()
{
goodGay gg;
gg.visit();
}
int main(){
test01();
system("pause");
return 0;
}
运算符重载
12、运算符重载
- 对于内置的数据类型的表达式的的运算符是不可能改变的(如1+1=2)
- 左移运算符属于ostream
- 占位参数的(int)使用,可以区分两个函数名相同的函数,重载
- 出现深拷贝时析构函数要发生变化
- 函数调用运算符,仿函数