C++学习笔记(十二)

一、对象的初始化和清理

生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全

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

1.1 构造函数和析构函数

对象的初始化和清理是两个非常重要的安全问题

        一个对象或者变量没有初始状态,对其使用后果是未知的

        使用完一个对象或者变量,没有及时清理,也会造成一定的安全问题

C++利用了构造函数和析构函数解决了上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。

对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。

构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。

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

构造函数语法:

1. 构造函数,没有返回值也不写void

2. 函数名称与类名相同

3. 构造函数可以有参数,因此可以发生重载

4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数语法:

1. 析构函数,没有返回值也不写void

2. 函数名称与类名相同,在名称前加上符号~

3. 析构函数不可以有参数,因此不可以发生重载

4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

#include 

using namespace std;

class Person
{
public:
	// 构造函数
	Person()
	{
		cout << "构造函数已调用......" << endl;
	}
	// 析构函数
	~Person()
	{
		cout << "析构函数已调用......" << endl;
	}
};

void test()
{
	Person p;
}

int main(int argc, char* argv[])
{
	test();
	return 0;
}

1.2 构造函数的分类及调用

两种分类方式:

        按参数分为:有参构造和无参构造

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

三种调用方式:

        括号法

        显示法

        隐式转换法

#include 

using namespace std;

class Person
{
public:
	// 无参构造函数(普通构造函数)
	Person()
	{
		cout << "无参构造函数已调用......" << endl;
	}
	// 有参构造函数(普通构造函数)
	Person(int p_age)
	{
		age = p_age;
		cout << "有参构造函数已调用......" << endl;
		cout << "年龄:" << age << endl;
	}
	// 拷贝构造函数
	Person(const Person &p)
	{
		age = p.age;
		cout << "拷贝构造函数已调用......" << endl;
		cout << "拷贝的年龄为:" << age << endl;
	}

	// 析构函数
	~Person()
	{
		cout << "析构函数已调用......" << endl;
	}
	int  age;
};

void test1()
{
	// 1. 括号法
	// 调用无参构造函数
	Person p1;
	// 调用有参构造函数
	Person p2(23);
	// 调用拷贝构造函数
	Person p3(p2);
	// 注意事项
	// 调用默认构造函数的时候,不能加(),因为下面这行代码,编译器会认为是一个函数的声明,不会认为在创建对象
	// Person p1();
}

void test2()
{
	// 2. 显示法
	// 调用无参构造函数
	Person p1;
	// 调用有参构造函数
	Person p2 = Person(23);
	// 调用拷贝构造函数
	Person p3 = Person(p2);
	// Person(23);  匿名函数 特点:当前行执行结束后,系统会立即回收掉匿名函数
	// 注意事项
	// 不要利用拷贝构造函数初始化匿名对象,编译器会认为 Person(p6) ===== Person p6;对象声明
	// Person(p6)
}

void test3()
{
	// 3. 隐式转换法
	// 调用无参构造函数
	Person p1;
	// 调用有参构造函数
	Person p2 = 23;
	// 调用拷贝构造函数
	Person p3 = p1;
}

int main(int argc, char* argv[])
{
	test1();
	cout << "------------------------" << endl;
	test2();
	cout << "------------------------" << endl;
	test3();
	cout << "------------------------" << endl;
	return 0;
}

1.3 拷贝构造函数调用时机

C++中拷贝构造函数调用的时机通常有三种情况

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

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

3. 以值方式返回局部对象

#include 

using namespace std;

class Person
{
public:
	Person()
	{
		cout << "无参构造函数已调用......" << endl;
	}
	Person(int p_age)
	{
		age = p_age;
		cout << "有参构造函数已调用......" << endl;
		cout << "年龄:" << age << endl;
	}
	Person(const Person &p)
	{
		age = p.age;
		cout << "拷贝构造函数已调用......" << endl;
		cout << "拷贝的年龄为:" << age << endl;
	}
	~Person()
	{
		cout << "析构函数已调用......" << endl;
	}
	int  age;
};

// 1. 使用一个已经创建完毕的对象来初始化一个新对象
void test1()
{
	Person p1(23);
	Person p2(p1);
}

// 2. 值传递的方式给函数参数传值
void doWork(Person p)
{

}

void test2()
{
	// 创建一个无参构造函数
	Person p;
	// 实参传递给形参的时候会创建一个拷贝构造函数
	doWork(p);
}

// 3. 值方式返回局部对象
Person doWork2()
{
	// 创建一个无参构造函数
	Person p1;
	// 局部对象以值的方式返回时,会创建一个新的拷贝构造函数
	return p1;
}
void test3()
{
	Person P = doWork2();
}

int main(int argc, char* argv[])
{
	test1();
	cout << "------------------------" << endl;
	test2();
	cout << "------------------------" << endl;
	test3();
	cout << "------------------------" << endl;
	return 0;
}

1.4 构造函数调用规则

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

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

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

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

构造函数调用规则如下:

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

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

1.5 深拷贝和浅拷贝

深浅拷贝是面试经典问题,也是常见的一个坑

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新深浅空间,进行拷贝操作

#include 

using namespace std;

class Person
{
public:
	Person(int m_age)
	{
		age = m_age;
		cout << "有参构造函数已调用......" << endl;
	}
	~Person()
	{
		cout << "析构函数已调用......" << endl;
	}
	int age;
};

void test()
{
	Person p1(18);
	cout << "p1的年龄为:" << p1.age << endl;
	Person p2(p1);
	cout << "p2的年龄为:" << p2.age << endl;
}

int main(int argc, char* argv[])
{
	test();
	return 0;
}

利用系统提供的默认拷贝构造函数进行普通的数据拷贝不会报错

但是当手动开辟堆区数据并在析构函数中进行释放时会报错

原因:利用编译器提供的默认拷贝构造函数会做浅拷贝操作,会把p1的成员属性逐字节的拷贝到p2中,p1和p2的指针指向了堆区同一地址的内容,堆栈的数据先进后出,所以p2先进行析构,把指向的堆区内存的内容释放干净,然后p1析构时仍然会对堆区的内容进行释放,暴露出来浅拷贝的问题:堆区的内容重复释放。浅拷贝的问题要利用深拷贝进行解决,再堆区重新申请一块内容用来存放相同的数据,p2指向此内存。

使用系统提供的默认拷贝构造函数(报错):

#include 

using namespace std;

class Person
{
public:
	Person(int m_age,int m_height)
	{
		age = m_age;
		height = new int(m_height);
		cout << "有参构造函数已调用......" << endl;
	}
	~Person()
	{
		// 由程序员手动开辟的堆区数据也需要程序员手动释放,在堆区数据销毁前释放
		// 堆区开辟的数据在析构函数执行完后销毁,所以应该析构函数内进行释放
		// 析构代码,将堆区开辟的数据做释放操作
		if (height != NULL)
		{
			delete height;
			height = NULL;
		}
		cout << "析构函数已调用......" << endl;
	}
	int age;
	int* height;
};

void test()
{
	Person p1(18,180);
	cout << "p1的年龄为:" << p1.age << endl;
	cout << "p1的身高为:" << *p1.height << endl;
	Person p2(p1);
	cout << "p2的年龄为:" << p2.age << endl;
	cout << "p2的身高为:" << *p2.height << endl;
}

int main(int argc, char* argv[])
{
	test();
	return 0;
}

使用自己实现的拷贝构造函数:

#include 

using namespace std;

class Person
{
public:
	Person(int m_age,int m_height)
	{
		age = m_age;
		height = new int(m_height);
		cout << "有参构造函数已调用......" << endl;
	}
	Person(const Person& p)
	{
		cout << "拷贝构造函数已调用......" << endl;
		age = p.age;
		// height = p.height; 编译器默认实现的就是这行代码 
		// 深拷贝操作
		height = new int(*p.height);
	}
	~Person()
	{
		// 由程序员手动开辟的堆区数据也需要程序员手动释放,在堆区数据销毁前释放
		// 堆区开辟的数据在析构函数执行完后销毁,所以应该析构函数内进行释放
		// 析构代码,将堆区开辟的数据做释放操作
		if (height != NULL)
		{
			delete height;
			height = NULL;
		}
		cout << "析构函数已调用......" << endl;
	}
	int age;
	int* height;
};

void test()
{
	Person p1(18,180);
	cout << "p1的年龄为:" << p1.age << endl;
	cout << "p1的身高为:" << *p1.height << endl;
	Person p2(p1);
	cout << "p2的年龄为:" << p2.age << endl;
	cout << "p2的身高为:" << *p2.height << endl;
}

int main(int argc, char* argv[])
{
	test();
	return 0;
}

总结:1. 浅拷贝:编译器提供的等号赋值操作        深拷贝:从堆区重新创建一块内容存放数据

           2. 析构代码的用处:将手动开辟的堆区数据进行释放操作

           3. 拷贝构造函数进行深拷贝可以避免堆区数据重复释放的问题

1.6 初始化列表

作用:C++提供了初始化列表语法,用来初始化属性

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

#include 
#include 

using namespace std;

class Person
{
public:
	Person(string m_name,int m_age,int m_height):name(m_name),age(m_age),height(m_height)
	{
		cout << "构造函数已调用......"  << endl;
	}
	~Person()
	{
		cout << "析构函数已调用......" << endl;
	}
	void printInfo()
	{
		cout << "姓名:" << name << endl;
		cout << "年龄:" << age << endl;
		cout << "身高:" << height << endl;
	}
private:
	string name;
	int age;
	int height;
};
int main(int argc, char* argv[])
{
	Person p("张三", 23, 180);
	p.printInfo();
	return 0;
}

1.7 类对象作为类成员

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

构造的顺序是:先调用对象成员的构造,在调用本类的构造

析构的顺序与构造相反

#include 
#include 

using namespace std;

class Phone
{
public:
	Phone(string name):phone_name(name)
	{
		cout << "Phone类的构造函数已调用......" << endl;
	}
	~Phone()
	{
		cout << "Phone类的析构函数已调用......" << endl;
	}
	string phone_name;
};
class Person
{
public:
	// 隐式转换法:phone(phone_name) ======= Phone phone = phone_name
	Person(string name,string phone_name):person_name(name),phone(phone_name)
	{
		cout << "Person类的构造函数已调用......" << endl;
	}
	~Person()
	{
		cout << "Person类的析构函数已调用......" << endl;
	}
	string person_name;
	Phone phone;
};

void test()
{
	Person p("张三", "Iphone 15 pro max远峰蓝");
}

int main(int argc, char* argv[])
{
	test();
	return 0;
}

1.8 静态成员

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

静态成员分为:

        静态成员变量:1. 所有对象共享同一份数据

                                 2. 在编译阶段分配内存

                                 3. 类内声明,类外初始化

        静态成员函数:1. 所有对象共享同一个函数

                                 2. 静态成员函数只能访问静态成员变量

静态成员变量:

#include 

using namespace std;

class Person
{
public:
	static int age;  // 静态成员变量

private:
	static int height;  // 静态成员变量 
};

// 类内声明,类外初始化
int Person::age = 23;
int Person::height = 180;

void test()
{
	// 所有对象共享同一份数据
	Person p1;
	cout << p1.age << endl;  // 23 
	Person p2;
	p2.age = 10;
	cout << p1.age << endl;  // 10
	
	// 静态成员变量两种访问方式
	// 1. 通过对象
	cout << p1.age << endl;
	// 2. 通过类名
	cout << Person::age << endl;

	// 静态成员变量也有访问权限
	// cout << p1.height << endl; p1.height不可访问
	// cout << Person::height << endl;  Person::height不可访问
}
int main(int argc, char* argv[])
{
	test();
	return 0;
}

静态成员函数:

#include 

using namespace std;

class Person
{
public:
	static void printInfo()
	{
		cout << "age:" << age << endl;  // 静态成员函数可以访问静态成员变量
		age = 100;
		cout << "age:" << age << endl;
		// cout << "height:" << height << endl;  报错,不可访问
		cout << "printInfo静态成员函数已调用......" << endl;
	}

	static int age;  // 静态成员变量
	int height;  // 非静态成员变量
private:
	static void writeInfo()
	{
		cout << "writeInfo静态成员函数已调用......" << endl;
	}
};

int Person::age = 23;

void test()
{	
	Person p1;
	// 静态成员函数两种访问方式
	// 1. 通过对象
	p1.printInfo();
	// 2. 通过类名
	Person::printInfo();

	// 静态成员函数也有访问权限
	// p1.writeInfo();  不可访问
	// Person::writeInfo();  不可访问
}
int main(int argc, char* argv[])
{
	test();
	return 0;
}

你可能感兴趣的:(c++,学习,笔记)