【C++基础】笔记3——类与对象1

本文详细介绍了C++中的类与对象,包括封装的概念,struct和class的区别,成员属性的私有化,对象的初始化和清理(构造函数和析构函数),以及深拷贝和浅拷贝的原理。此外,还讨论了C++对象模型中的this指针,常量成员函数,友元机制,以及运算符重载,如加号、左移、递增和关系运算符的重载。
摘要由CSDN通过智能技术生成

类与对象

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

封装

意义:将属性和行为作为一个整体,表现生活中的事务,将属性和行为加以权限控制
语法:class 类名{ 访问权限: 属性/行为 };

const double PI = 3.14;		//圆周率

class Circle {
public:
	//属性
	int m_r;		//半径

	//行为(用函数)
	double calZC() {		//计算周长
		return 2 * PI * m_r;
	}
};

int main()
{
	//通过圆类,创建具体的圆(对象)
	Circle c1;
	//给圆对象的属性进行赋值
	c1.m_r = 10;
	cout << "圆c1的周长" << c1.calZC() << endl;
}

实例化:通过一个类创建一个对象的过程

class Student {
public:
	//类中的属性和行为,统称为成员
	//属性 = 成员属性 = 成员变量		行为 = 成员函数 = 成员方法
	string m_Name;
	int m_ID;

	void setName(string name) {		//给姓名赋值
		m_Name = name;
	}

	void setID(int ID) {
		m_ID = ID;
	}

	void showStudent() {			//打印学生信息
		cout << "姓名:" << m_Name << "		学号:" << m_ID << endl;
	}
};

int main()
{
	Student s1;
	s1.setName("费沁源");
	s1.setID(1);
	s1.showStudent();
}

访问权限:
1.公共权限 public
成员在类内可以访问,类外也可以访问
待补充(继承)
2.保护权限 protected
成员在类内可以访问,类外不可以访问
子类可以访问父类的保护内容

3.私有权限 private
成员在类内可以访问,类外不可以访问
子类不可以访问父类的私有内容

struct和class的区别

struct默认权限为共有; class默认权限为私有
(struct也可以包含成员函数,可以继承,可以实现多态)
class可用于定义模板参数,struct不可以

将成员属性设置为私有

优点:
1.可以自己控制读写权限
2.可以检测数据的有效性

class Person {
private:
	string m_Name;		//姓名	可读可写
	char m_Sex;			//性别	可读
	int m_Age;			//年龄	可读可写(对写入进行限制)
	string m_Lover;		//伴侣	可写

public:
	void setName(string name) {		//设置姓名
		m_Name = name;
	}

	string getName() {				//获取姓名
		return m_Name;
	}

	int getAge() {					//获取年龄
		m_Age = -1;					//初始化为-1
		return m_Age;
	}
	
	void setAge(int age) {			//设置年龄,可检验数值的有效性
		if (age < 0 || age > 150) {
			cout << "数据无效" << endl;
			return;
		}
		m_Age = age;
	}

	void setLover(string lover) {	//设置伴侣
		m_Lover = lover;
	}
};

对象的初始化和清理

C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置

C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数

语法:类名(){ }
1.构造函数没有返回值,也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时会自动调用构造,无须手动调用,而且只会调用一次

析构函数

语法:~类名(){ }
1.构造函数没有返回值,也不写void
2.函数名称与类名相同,在名称前加上~
3.构造函数不可以有参数,因此不可以发生重载
4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

构造函数的分类及调用

两种分类方式:
按参数分:有参构造 和 无参构造(默认构造)
按类型分:普通构造 和 拷贝构造

//有参构造
Person() {
	cout << "Person 无参构造函数调用" << endl;
}
//无参构造(默认构造)
Person(int a) {
	age = a;
	cout << "Person 有参构造函数调用" << endl;
}
//拷贝构造函数
Person(const Person &p) {		//复制一个一模一样的,不能改变本体,所以使用const
	age = p.age;
	cout << "Person 拷贝构造函数调用" << endl;
}

三种调用方式:
括号法 显示法 隐式转换法

//括号法
//调用默认构造函数时不要加“()“
//编译器会将 Person p1(); 当作函数声明,不会认为在创建对象
Person p1;		//默认构造函数调用
Person p2(10);	//有参构造函数调用
Person p3(p2);	//拷贝构造函数调用
//显示法
Person p1;					//默认构造函数调用
Person p2 = Person(10);		//有参构造函数调用
Person p3 = Person(p2);		//拷贝构造函数调用

Person(10);		//匿名对象,47行代码中,p2为它的名字
//特点:当前行执行结束后,系统会立即回收匿名对象

//不要利用拷贝构造函数初始化匿名对象
Person(p3);		//错误
//编译器会认为Person(p3) == Person p3;看作一个对象的声明,
//隐式转换法
Person p4 = 10;		//相当于写了 Person p4 = Person(10);	有参构造
Person p5 = p4;		//拷贝构造
拷贝构造函数调用时机

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

void test1()
{
	Person p1(20);
	Person p2(p1);

	cout << "p2的年龄" << endl;
}

2.值传递的方式给函数参数传值

void func(Person p)		//值传递的本质:会拷贝出一个临时的副本
{						//等于Person p = p;		拷贝构造函数的隐式写法

}

void test2()
{
	Person p;
	func(p);
}

在这里插入图片描述
3.值方式返回局部对象

Person func2()
{
	Person p3;
	cout << (int*)&p3 << endl;
	return p3;
}
void test3()
{
	Person p = func2();
	cout << (int*)&p << endl;
}

在这里插入图片描述

构造函数调用规则

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

  • 1.默认构造函数(无参,函数体为空)
  • 2.默认析构函数(无参,函数体为空)
  • 3.默认拷贝构造函数,对属性进行值拷贝
  • 4.赋值运算符operator=,对属性进行值拷贝

构造函数调用规则:

  • 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
深拷贝与浅拷贝
  • 浅拷贝:简单的赋值拷贝操作
  • 深拷贝:在堆区重新申请空间,进行拷贝操作
class Person {
public:
	Person() {
		cout << "Person 默认构造函数调用" << endl;
	}
	Person(int age, int height) {
		m_Age = age;
		m_Height = new int(height);			//用指针接收堆区的数据
		cout << "Person 有参构造函数调用" << endl;
	}
	~Person() {
		//析构函数的作用:将堆区开辟的数据做释放操作
		if (m_Height != NULL) {
			delete m_Height;
			m_Height = NULL;		//防止野指针,进行置空
		}
		cout << "Person 析构函数的调用" << endl;
	}
	int m_Age;
	int *m_Height;		//身高
};

void test1()
{
	Person p1(18, 160);
	cout << "p1的年龄为:" << p1.m_Age << "p1的身高为:" << *p1.m_Height << endl;
	Person p2(p1);
	cout << "p2的年龄为:" << p1.m_Age << "p2的身高为:" << *p1.m_Height << endl;
}

浅拷贝带来的问题:堆区内存的重复释放
浅拷贝是逐个字节完全复制,所以p2.m_Height的地址和p1.m_Height一样,当p2被释放时,执行析构函数,对p2.m_Height进行释放,当p1被释放时,执行析构函数,会对0x0011再次进行释放,从而导致错误
在这里插入图片描述
深拷贝:

//自己写拷贝构造函数 解决浅拷贝带来的问题
Person(const Person &p) {
	cout << "Person拷贝函数的调用" << endl;
	m_Age = p.m_Age;
	//深拷贝操作
	m_Height = new int(*p.m_Height);
}
初始化列表

C++提供了初始化列表语法,用来初始化属性
语法:构造函数 () : 属性1(值1)属性2(值2)…{}

Person(int age, int score) :m_Age(age), m_Score(score) {
}
类对象作为类成员

C++中的成员可以是另一个类的对象,称为成员对象

class A{};
class B{
	A a;
};

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

class Phone {
public:
	string m_PName;

	Phone(string pName) {
		m_PName = pName;
		cout << "Phone 构造函数调用" << endl;
	}
	~Phone() {
		cout << "Phone 析构函数调用" << endl;
	}
};

class Person {
public:
	string m_Name;
	Phone m_Phone;

	Person(string name, string pname) :m_Name(name), m_Phone(pname) {
		cout << "Person 构造函数调用" << endl;
	}
	~Person() {
		cout << "Person 析构函数调用" << endl;
	}
};

在这里插入图片描述

静态成员

静态成员就是在静态成员变量和静态成员函数前加上static关键字

  • 静态成员变量
    所有对象共享一份数据
    在编译阶段分配内存
    类内声明,类外初始化
  • 静态成员函数
    所有对象共享同一个函数
    静态成员函数只能访问静态成员变量
    静态成员函数也有访问权限(public,private)

static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为 静态成员变量分配一份内存,所有对象使用的都是这份内存中的数据。当某个对象修改了静态成员变量,也会影响到其他对象。

C++对象模型和this指针

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

在C++中,类内的成员变量和成员函数是分开存储的
只有非静态成员变量才属于类的对象上

p占1个字节
空对象占用内存空间为1
C++编译器会给每个空对象分配1字节的空间,为了区分空对象占内存的位置
每个空对象也应该有一个独一无二的内存地址

class Person {

};

void test()
{
	Person p;
	cout << "size of p = " << sizeof(p) << endl;
}

p占4个字节

class Person {
	int m_A;		//非静态成员变量,属于类的对象上
	static int m_B;	//静态成员变量(类内声明,类外初始化),不属于类的对象上
	void func() {	//非静态成员函数,不属于类的对象上
	}
	static void func1() {	//静态成员函数,不属于类的对象上
	}
};

void test()
{
	Person p;
	cout << "size of p = " << sizeof(p) << endl;
}
this指针

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

C++提供特殊的对象指针this,解决上述问题
this指针指向被调用的成员函数所属的对象(谁调用this,this指向谁)
this指针是隐含每一个非静态成员函数内的一种指针(不需要传数据)
this指针不需要定义,直接使用即可

this指针的用途:
当形参和成员变量同名时,可用this指针来区分
在类的非静态成员函数中返回对象本身,可使用return *this

解决名称冲突
此时p1的年龄18不能够成功输出

class Person {
public:
	int age;

	Person(int age) {	//编译器会认为三个age是同一个age,与成员属性age无关
		age = age;
	}
};

//解决名称冲突	
void test0()
{
	Person p1(18);
	cout << "p1的年龄为:" << p1.age << endl;
}

利用this解决名称冲突问题
此时this指向的age与成员属性age一致
p1的年龄18可以正常输出

class Person {
public:
	int age;

	Person(int age) {	
		this->age = age;
	}
};

返回对象本身用*this

class Person {
public:
	int age;

	Person(int age) {	//编译器会认为9-10行中的三个age是同一个age,与7行的成员属性age无关
		this->age = age;
	}
	Person& PersonAddAge(Person &p) {	//用引用返回
		this->age += p.age;

		//this指向p3的指针,而*this指向的就是p2这个对象本体
		return *this;
	}
};

//返回对象本身用*this
void test1()
{
	Person p2(10);
	Person p3(10);

	//链式编程思想
	p3.PersonAddAge(p2).PersonAddAge(p2).PersonAddAge(p2);
	cout << "p3 age = " << p3.age << endl;
}
空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性

class Person {
public:
	void showClassName() {
		cout << "this is Person class" << endl;
	}
void showPersonAge() {
	cout << "age = " << m_Age << endl;
}

	int m_Age;
};

void test0()
{
	Person *p = NULL;

	p->showClassName();		//可以正常调用
	p->showPersonAge();		//不可以正常调用
}

其中,showPersonAge函数实际上加上了this指针

	void showPersonAge() {
		cout << "age = " << this->m_Age << endl;
	}

报错的原因是传入的为NULL指针,只要加入判断就可以避免

	void showPersonAge() {
		if (this->m_Age == NULL) {
			return;
		}
		cout << "age = " << m_Age << endl;
	}
const修饰成员函数

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

class Person {
public:
	int m_A;
	mutable int m_B;	//特殊变量,即使在常函数中,也可以修改

	//this指针的本质是指针常量,指针的指向不可以修改
	//const Person * const this
	//在成员函数后面加上const,修饰的是this指向,让指针指向的值也不可以修改
	void func() const {
		m_A = 100;		//错误		等同于 this->m_A = 100;
		m_B = 100;		//正确		等同于 this->m_B = 100;
	}
	void func2() {
		m_A = 10;	
		m_B = 10;
	}
};

常对象
声明对象前加const称为常对象
常对象只能调用常函数

void test0()
{
	const Person p;		//在对象前加const,变为常对象
	p.m_A = 100;		//错误
	p.m_B = 100;		//正确

	//常对象只能调用常函数
	//常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
	p.func();			//错误
	p.func2();			//正确
}

友元

在程序里,有些私有属性,想让类外的一些函数或者类进行访问,需要用到友元技术(客厅public卧室private和好朋友friend)

友元的目的就是让一个函数或者类,访问另一个类中的私有成员

友元的关键字:friend

友元的三种实现:
1.全局函数做友元

class Building {
	//goodFriend全局函数是building类的友元,可以访问Building中私有成员
	//只要写在类的最上面就行
	friend void goodFriend(Building *building);
public:
	string m_Livingroom;	//客厅

private:
	string m_Bedroom;		//卧室

public:
	Building() {
		m_Livingroom = "客厅";
		m_Bedroom = "卧室";
	}
};

//全局函数
void goodFriend(Building *building)
{
	cout << "好朋友全局函数,正在访问:" << building->m_Livingroom << endl;

	cout << "好朋友全局函数,正在访问:" << building->m_Bedroom << endl;
}

void test0()
{
	Building b;
	goodFriend(&b);
}

2.类做友元

class Building;		//类声明

class GoodFriend {
public:
	Building *building;

	GoodFriend();
	void visit();	//参观函数,访问Building中的属性
};

class Building {
	friend class GoodFriend;
public:
	string m_Livingroom;	//客厅
private:
	string m_Bedroom;		//卧室

public:
	Building();
};

//类外写成员函数
Building::Building()
{
	m_Livingroom = "客厅";
	m_Bedroom = "卧室";
}

GoodFriend::GoodFriend()
{
	//创建建筑物对象
	building = new Building;
}

void GoodFriend::visit()
{
	cout << "好朋友全局函数,正在访问:" << building->m_Livingroom << endl;

	cout << "好朋友全局函数,正在访问:" << building->m_Bedroom << endl;
}

void test0()
{
	GoodFriend gf;
	gf.visit();
}

3.成员函数做友元

class Building;

class GoodFriend {
public:
	GoodFriend();
	void visit();		//让visit函数可以访问Building中私有成员
	void visit2();		//visit2函数不可以

	Building *building;
};

class Building {
	//GoodFriend类下的visit函数作为本类的好朋友,可以访问私有的成员
	friend void GoodFriend::visit();
public:
	string m_Livingroom;	//客厅
private:
	string m_Bedroom;		//卧室

public:
	Building();
};

GoodFriend::GoodFriend()
{
	building = new Building;
}

void GoodFriend::visit()
{
	cout << "visit函数正在访问:" << building->m_Livingroom << endl;
	cout << "visit函数正在访问:" << building->m_Bedroom << endl;
}

void GoodFriend::visit2()
{
	cout << "visit函数正在访问:" << building->m_Livingroom << endl;
}

Building::Building()
{
	m_Livingroom = "客厅";
	m_Bedroom = "卧室";
}

void test0()
{
	GoodFriend g;
	g.visit();
	g.visit2();
}

运算符重载

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

加号运算符重载 +

作用:实现两个自定义数据类型相加的运算
在这里插入图片描述
成员函数重载

class Person {
public:
	int m_A;
	int m_B;

	//成员函数重载+号
	//本质:Person p3 = p1.operator+(p2);
	Person operator+ (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 p3 = operator+(p1, p2);
Person operator+ (Person &p1, Person &p2)
{
	Person temp;
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}

//运算符重载也可以函数重载
Person operator+ (Person &p1, int i)
{
	Person temp;
	temp.m_A = p1.m_A + i;
	temp.m_B = p1.m_B + i;
	return temp;
}

注意:
1.对于内置的数据类型的表达式的运算符是不可能改变的
2.不要滥用运算符重载

左移运算符重载 <<

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

同常不会用成员函数重载左移运算符,因为cout在左侧

//operator<<(cout) 简化版本:p << cout;
void operator<< (cout){}

只能利用全局函数左移运算符

void operator<<(ostream &cout, Person &p)		//本质:operator<<(cout, p)	简化为cout << p;
{	//ostream 输出流,cout只能有一个,所以用引用的方式
	cout << "m_A = " << p.m_A << "m_B = " << p.m_B;
}
void test0()
{
	Person p;
	cout << p;
}

如果使用上述方法进行左移运算符重载,不能进行“追加”

cout << p;			//正确
cout << p << endl;	//错误

这种“追加”的思想是链式编程的思想,返回值应该和cout一致

ostream & operator<<(ostream &cout, Person &p)		//本质:operator<<(cout, p)	简化为cout << p;
{	//ostream 输出流,cout只能有一个,所以用引用的方式
	cout << "m_A = " << p.m_A << "m_B = " << p.m_B;
	return cout;
}
递增运算符重载++

不能用编译器提供的赋值(类似于浅拷贝)原因:与深浅拷贝相同,堆区内存的重复释放,导致程序崩溃

class Person {
public:
	int *m_Age;

	Person(int age) {
		m_Age = new int(age);
	}
	~Person(){
		if (m_Age != NULL) {
			delete m_Age;
			m_Age = NULL;
		}
	}

	//重载赋值运算符
	Person& operator=(Person &p) {
		//应先判断是否有属性在堆区,如果有,先释放干净,然后再深拷贝
		if (m_Age != NULL) {
			delete m_Age;
			m_Age = NULL;
		}
		//深拷贝
		m_Age = new int(*p.m_Age);
		return *this;
	}
};

void test0()
{
	Person p1(18);
	Person p2(20);
	Person p3(22);
	
	p3 = p2 = p1;

	cout << "p1的年龄为" << *p1.m_Age << endl;
	cout << "p2的年龄为" << *p2.m_Age << endl;
	cout << "p3的年龄为" << *p3.m_Age << endl;
}
关系运算符重载== !=

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

class Person {
public:
	string m_Name;
	int m_Age;

	Person(string name, int age) {
		m_Name = name;
		m_Age = age;
	}

	bool operator==(Person &p) {
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
			return true;
		}
		return false;
	}
	bool operator!=(Person &p) {
		if (this->m_Name != p.m_Name && this->m_Age != p.m_Age) {
			return true;
		}
		return false;
	}
};
函数调用运算符重载()

由于重载后使用的方式和函数的调用非常相似,因此称为仿函数
仿函数没有固定写法,非常灵活

class MyPrint {
public:
	//重载函数调用运算符
	void operator()(string test) {
		cout << test << endl;
	}
};

void test0()
{
	MyPrint mp;
	mp("HelloWorld");
}

class MyAdd {
public:
	int operator()(int num1, int num2) {
		return num1 + num2;
	}
};

void test1()
{
	MyAdd ma;
	int ret = ma(100, 20);
	cout << ret << endl;

	//匿名对象
	cout << MyAdd()(100, 20) << endl;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值