C++封装、构造和析构、this指针、友元、运算符重载(黑马程序员教程笔记)

目录

一、封装

1、封装的意义

2、struct和class区别

3、成员属性设置为私有

二、对象的初始化和清理

1.构造函数和析构函数

2.构造函数的分类及调用

3.拷贝构造函数调用时机

4.构造函数调用规则

5.深拷贝与浅拷贝

6.初始化列表

7.类对象作为类成员

8.静态成员

三、C++对象模型和this指针

1.成员变量和成员函数分开存储

2.this指针概念

3.空指针访问成员函数

4.const修饰成员函数

四、友元

1.基本概念

2.全局函数做友元

3.类做友元

4.成员函数做友元

五、运算符重载

1.基本概念

2.加号运算符重载

3.左移运算符重载

4.递增运算符重载

5.赋值运算符重载

6.关系运算符重载

7.函数调用运算符重载


C++面向对象的三大特性为:封装、继承、多态

C++认为万事万物皆为对象,对象上有其属性和行为。

具有相同性质的对象可抽象为类。

一、封装

1、封装的意义

(1)将属性和行为作为一个整体,表现生活中的事物。

语法:class  类名 { 访问权限:属性/行为 };

注意:类中的属性和行为统称为成员

           属性:成员属性/成员变量

           行为:成员函数/成员方法

举例1:设计一个圆类,求圆的周长

#include<iostream>
using namespace std;//命名空间
const double PI = 3.14;

class Circle
{
	//访问权限
public:
	//属性
	int r;
	//行为
	double caclulate()
	{
		return 2 * PI * r;
	}
};
int main()
{
	//通过圆类创建具体的圆(对象)——实例化
	Circle c1;
	c1.r = 10;
	cout << "圆的周长" << c1.caclulate() << endl;
	system("pause");
	return 0;
}

举例2:设计一个学生类,包含姓名和学号

#include<iostream>
using namespace std;//命名空间
#include<string.h>

class Student
{
	//访问权限
public:
	//属性
	string name;
	int id;
	//行为
	void show()
	{
		cout << "学生的姓名" << name << "学生的学号" << id << endl;
	}
};
int main()
{
	//实例化
	Student s;
	s.name = "张三";
	s.id = 10;
	s.show();
	system("pause");
	return 0;
}

(2)将属性和行为加以权限控制:

public公共权限:类内可以访问、类内可以访问

protected保护权限:类内可以访问、类外不可以访问、子类可以访问父类的保护内容

private私有权限:类内可以访问、类外不可以访问、子类不可以访问父类的私有内容

2、struct和class区别

唯一区别:默认的访问权限不同:struct默认权限为公共、class默认权限为私有

3、成员属性设置为私有

优点1:将所有成员属性设置为私有,可以自己控制读写权限

优点2:对于写权限,我们可以检测数据的有效性

举例:创建立方体类、设计属性、设计行为:获取立方体面积和体积、利用全局函数和成员函数判断两个立方体是否相等

#include<iostream>
using namespace std;//命名空间
#include<string.h>
class Cube
{
private:
	int l;
	int w;
	int h;
public:
	void setl(int m_l)
	{
		l = m_l;
	}
	int getl()
	{
		return l;
	}
	void setw(int m_w)
	{
		w = m_w;
	}
	int getw()
	{
		return w;
	}
	void seth(int m_h)
	{
		h = m_h;
	}
	int geth()
	{
		return h;
	}
	int gets()
	{
		return 2 * (l * w + l * h + h * w);
	}
	int getv()
	{
		return l * w * h;
	}

	bool issame(Cube& c)
	{
		if (l == c.getl() && h == c.geth() && w == c.getw())
		{
			return true;
		}
		return false;
	}



};
//利用全局函数判断两个立方体是否相等
bool issame(Cube& c1, Cube& c2)
{
	if (c1.getl() == c2.getl() && c1.geth() == c2.geth() && c1.getw() == c2.getw())
	{
		return true;
	}
	return false;
}
int main()
{
	//实例化
	Cube c1;
	c1.seth(10);
	c1.setl(10);
	c1.setw(10);
	cout << "c1的面积为" << c1.gets() << " c1的体积为" << c1.getv() << endl;
	Cube c2;
	c2.seth(10);
	c2.setl(10);
	c2.setw(10);
	//利用全局函数
	bool ret = issame(c1, c2);
	if (ret)
	{
		cout << "c1和c2相等" << endl;
	}
	else
	{
		cout << "c1和c2不相等" << endl;
	}

	//利用成员函数
	ret = c1.issame(c2);
	if (ret)
	{
		cout << "c1和c2相等" << endl;
	}
	else
	{
		cout << "c1和c2不相等" << endl;
	}

	system("pause");
	return 0;
}

二、对象的初始化和清理

1.构造函数和析构函数

(1)构造函数和析构函数会被编译器自动调用,完成对象初始化和清理工作,如果我们不提供构造和析构函数,编译器会提供(空实现)。

(2)构造函数:

作用:主要作用在于创建对象时为对象的成员属性赋值。

语法:类名(){ }

注意:没有返回值也不写void、函数名称与类名相同、构造函数可以有参数,因此可以发生重载、程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次。

(3)析构函数:

作用:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

语法:~类名(){ }

注意:析构函数,没有返回值也不写void、函数名称与类名相同,在名称前加~、析构函数不可以有参数,因此不可以发生重载、程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次。

2.构造函数的分类及调用

(1)两种分类方式

按参数分为:有参构造和无参构造(默认构造)

按类型分类:普通构造和拷贝构造

(2)三种调用方式

括号法(默认构造函数调用)、显示法、隐式转换法

class Person
{
public:
	Person()
	{
		cout << "无参构造函数" << endl;
	}
	Person(int a)
	{
		age = a;
		cout << "有参构造函数" << endl;
	}
	Person(const Person& p)
	{
		cout << "拷贝构造函数" << endl;
	}

	~Person()
	{
		cout << "析构函数" << endl;
	}
public:
	int age;
};

void test01()
{
	Person p;//注意调用无参构造函数时不能加括号,如果加了编译器认为这是一个函数声明

	Person p1(10);//括号法

	Person p2 = Person(10);//显式法
	Person p3 = Person(p2);

	Person p4 = 10;//隐式转换法:Person p4 = Person(10);
	Person p5 = p4;
}

3.拷贝构造函数调用时机

(1)使用一个已经创建完毕的对象来初始化一个新对象

(2)值传递的方式给函数参数传值

(3)以值方式返回局部对象

class Person
{
public:
	Person()
	{
		cout << "无参构造函数" << endl;
	}
	Person(int a)
	{
		age = a;
		cout << "有参构造函数" << endl;
	}
	Person(const Person& p)
	{
		age = p.age;
		cout << "拷贝构造函数" << endl;
	}

	~Person()
	{
		cout << "析构函数" << endl;
	}
public:
	int age;
};
//(1)使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
	Person p1(20);
	Person p2(p1);
	cout << "p2的年龄为:" << p2.age << endl;
}
//(2)值传递的方式给函数参数传值
void dowork(Person p)
{
}
void test02()
{
	Person p;
	dowork(p);
}
//(3)以值方式返回局部对象
Person dowork1()
{
	Person p;
	return p;
}
void test03()
{
	Person p = dowork1();
}

4.构造函数调用规则

默认情况下c++编译器至少给一个类添加3个函数

(1)默认构造函数(无参,函数体为空)

(2)默认析构函数(无参,函数体为空)

(3)默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

如果用户定义有参构造函数,c++不再提供无参构造,但是会提供默认拷贝构造

如果用户定义拷贝构造函数,c++不会再提供其他构造函数

5.深拷贝与浅拷贝

(1)浅拷贝:简单的复制拷贝操作

(2)深拷贝:在堆区重新申请空间,进行拷贝操作

6.初始化列表

(1)作用:c++提供了初始化列表语法,用来初始化属性

(2)语法:构造函数():属性1(值1),属性2(值2)...{ }

Person(int a,int b,int c) :m_A(a), m_B(b), m_C(c)
{

}

7.类对象作为类成员

c++类中的成员可以是另一个类的对象,我们称该成员为对象成员。

当其它类对象作为本类成员,构造时先构造类对象,再构造自身,析构的顺序相反。

	class A 
	{
	}
	class B
	{
		A a;
	}

8.静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员,分为:

(1)静态成员变量:

所有对象共享同一份数据、在编译阶段分配内存、类内声明,类外初始化

class Person
{
public:
	static int m_A;
};
int Person::m_A = 100;
void test1()
{
	Person p;
	cout << p.m_A << endl;
	Person p2;
	p2.m_A = 200;
	cout << p.m_A << endl;//共享
}
void test2()
{
	//静态成员变量不属于某个对象上,所有对象共享同一份数据
	//因此静态成员变量有两种访问方式
	//1.通过对象进行访问
	Person p;
	cout << p.m_A << endl;
	//2.通过类名进行访问
	cout << Person::m_A << endl;
}
int main()
{
	test2();
	system("pause");
	return 0;
}
//注:静态成员变量也有访问权限:private类外无法访问

(2)静态成员函数:

所有对象共享同一个函数、静态成员函数只能访问静态成员变量

class Person
{
public:
	static void func()
	{
		m_A = 100;
		cout << "静态成员函数的调用" << endl;
	}
	static int m_A;
	int m_B;
};
int Person::m_A=0;
void test1()
{
	//1.通过对象进行访问
	Person p;
	p.func();
	//2.通过类名进行访问
	Person::func();
}
int main()
{
	test1();
	system("pause");
	return 0;
}

三、C++对象模型和this指针

1.成员变量和成员函数分开存储

在c++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上(可以通过sizeof验证)(空对象占用内存:1)

2.this指针概念

(1)每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型对象会共用一块代码,那么问题是:这一块代码是如何分哪个对象调用自己的?

(2)c++通过提供特殊的对象指针,this指针,解决上述问题,this指针指向被调用的成员函数所属的对象

(3)this指针是隐含每一个非静态成员函数内的一种指针

(4)this指针不要定义,直接使用即可

(5)this指针的用途:

当形参和成员变量同名时,可以用this指针区分

在类的非静态成员函数中返回对象本身,可使用return*this

class Person
{
public:
	 Person (int age)
	 {
		 this->age = age;//this指针指向被调用的成员函数所属的对象:p
	 }
	 Person& PersonAddAge(Person &p)
	 {
		 this->age += p.age;
		 //this指向p2的指针,而*this指向的就是p2这个对象本体
		 return *this;
	 }
	 int age;
};
void test1()
{
	Person p(18);
	cout << "p年龄为:" << p.age << endl;
}
void test2()
{
	Person p1(18);
	Person p2(18);
	//链式编程思想
	p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
	cout << "p2年龄为:" << p2.age << endl;
}
int main()
{
	test2();
	system("pause");
	return 0;
}

3.空指针访问成员函数

(1)c++中空指针是可以调用成员函数的,但是也要注意有没有用到this指针

(2)如果用到this指针,需要加以判断保证代码的健壮性

4.const修饰成员函数

(1)常函数:

成员函数后加const后我们称这个函数为常函数(如:void show() const )

常函数内不可以修改成员属性(修饰的是this指向,让指针指向的值也不可以修改)

特例:成员属性声明时加关键字mutable后,在常函数中依然可以修改

(2)常对象:

声明对象前加const称该对象为常对象(如:const Person p;)

常对象只能调用常函数

四、友元

1.基本概念

(1)目的:让一个函数或者类访问另一个类中私有成员

(2)关键字:friend

(3)三种实现:全局函数做友元、类做友元、成员函数做友元

2.全局函数做友元

#include<iostream>
using namespace std;
#include<string.h>
class Building
{
	//test1全局函数是Building好朋友,可以访问Building中私有成员
	friend void test1(Building* building);
public:
	Building()
	{
		m_SittingRoom = "客厅";
		m_Bedroom = "卧室";
	}
public:
	string m_SittingRoom;//客厅
private:
	string m_Bedroom;
};
//全局函数
void test1(Building *building)
{
	cout << "全局函数正在访问:" <<building->m_Bedroom << endl;
}
int main()
{
	Building building;
	test1(&building);
	system("pause");
	return 0;
}

3.类做友元

#include<iostream>
using namespace std;
#include<string.h>
class Building;
class Friend
{
public:
	Friend();
	void visit();//参观函数 访问Building中属性
	Building *building;
};
class Building
{
	//Friend类是本类的好朋友,可以访问本类中私有成员
	friend class Friend;
public:
	Building();
public:
	string m_SittingRoom;//客厅
private:
	string m_Bedroom;
};
//类外写成员函数
Building::Building()
{
	m_SittingRoom = "客厅";
	m_Bedroom = "卧室";
}
Friend::Friend()
{
	//创建建筑物对象
	building = new Building;//在堆区创建一个对象
}
void Friend::visit()
{
	cout<< "Friend类正在访问:" << building->m_SittingRoom << endl;
	cout<< "Friend类正在访问:" << building->m_Bedroom << endl;
}
void test1()
{
	Friend f;
	f.visit();
}
int main()
{
	Building building;
	test1();
	system("pause");
	return 0;
}

4.成员函数做友元

#include<iostream>
using namespace std;
#include<string.h>
class Building;
class Friend
{
public:
	Friend();
	void visit1();//参观函数 访问Building中私有成员
	void visit2();//参观函数 不可以访问Building中私有成员
	Building* building;
};
class Building
{
	//Friend类下的visit1成员函数是本类的好朋友,可以访问本类中私有成员
	friend void Friend::visit1();
public:
	Building();
public:
	string m_SittingRoom;//客厅
private:
	string m_Bedroom;
};
//类外写成员函数
Building::Building()
{
	m_SittingRoom = "客厅";
	m_Bedroom = "卧室";
}
Friend::Friend()
{
	//创建建筑物对象
	building = new Building;//在堆区创建一个对象
}
void Friend::visit1()
{
	cout << "Friend类正在访问:" << building->m_SittingRoom << endl;
	cout << "Friend类正在访问:" << building->m_Bedroom << endl;
}
void Friend::visit2()
{
	cout << "Friend类正在访问:" << building->m_SittingRoom << endl;
	//cout << "Friend类正在访问:" << building->m_Bedroom << endl;
}
void test1()
{
	Friend f;
	f.visit1();
	f.visit2();
}
int main()
{
	Building building;
	test1();
	system("pause");
	return 0;
}

五、运算符重载

1.基本概念

运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

2.加号运算符重载

作用:实现两个自定义数据类型相加的运算

#include<iostream>
using namespace std;
//通过自己写成员函数,实现两个对象相加属性后返回新的对象
class Person
{
public:
	int m_a;
	int m_b;
public:
	Person() {};
	Person(int a, int b)
	{
		this->m_a = a;
		this->m_b = b;
	}
	Person operator+ (const Person& p);
};
//成员函数
Person Person::operator+(const Person& p)
{
	Person temp;
	temp.m_a = this->m_a + p.m_a;
	temp.m_b = this->m_b + p.m_b;
	return temp;
}
//全局函数
Person operator+(const Person &p1, const Person& p2)
{
	Person temp;
	temp.m_a = p1.m_a + p2.m_a;
	temp.m_b = p1.m_b + p2.m_b;
	return temp;
}
void test1()
{
	Person p1(10,10);
	Person p2(20,20);
	
	Person p3 = p1 + p2;//Person p3 = p1.operator+(p2);
	Person p4 = p1 + p2;//Person p4 =operator+(p1,p2);
	cout << p3.m_a << endl;
	cout << p3.m_b << endl;
	cout << p4.m_a << endl;
	cout << p4.m_b << endl;
}
int main()
{
	test1();
	system("pause");
	return 0;
}

3.左移运算符重载

作用:可以输出自定义数据类型

#include<iostream>
using namespace std;
class Person
{
public:
	//不会利用成员函数重载左移运算符,因为无法实现cout在左侧
	int m_a;
	int m_b;
};
ostream &operator<<(ostream &cout, Person &p)//cout<<p
{
	cout << "m_a=" << p.m_a << " m_b=" << p.m_b;
	return cout;//cout可 out也可
}
void test1()
{
	Person p;
	p.m_a = 10;
	p.m_b = 10;
	cout << p << endl;
}
int main()
{
	test1();
	system("pause");
	return 0;
}

4.递增运算符重载

作用:通过重载递增运算符,实现自己的整型数据(前置递增返回引用,后置递增返回值)

5.赋值运算符重载

c++编译器至少给一个类添加四个函数

(1)默认构造函数(无参,函数体为空)

(2)默认析构函数(无参,函数体为空)

(3)默认拷贝构造函数,对属性进行值拷贝

(4)赋值运算符operator=,对属性进行值拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

6.关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作

7.函数调用运算符重载

函数调用运算符()也可以重载

由于重载后使用的方式非常像函数的调用,因此称为仿函数

仿函数没有固定写法,非常灵活

教程地址:黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难_哔哩哔哩_bilibili

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值