C++ 核心篇 5 之类与对象 - 对象特性

前言

C++ 来源于生活,每个对象都会有初始设置以及销毁前的清除数据操作


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

一个对象或变量没有初始状态,对其使用结果是未知的;使用完变量或对象后,没有及时清理也会造成一定的安全隐患

构造函数和析构函数

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


构造函数

构造函数:主要作用在于创建对象时为对象的属性赋值(构造函数由编译器自动调用)

构造函数语法:类名() { 代码体 }

构造函数特点:没有返回值也不用写 void;函数名称与类名称一致;构造函数可以有参数,因此可以发生重载;程序在调用对象时会自动执行一次构造函数的调用

析构函数

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

析构函数语法:~类名() { 代码体 }

构造函数特点:没有返回值也不用写 void;函数名称与类名称一致,还需要在前面加上 ~;构造函数不可以有参数,因此不可以发生重载;程序会在对象销毁前调用一次析构函数


类中构造函数和析构函数的实际调用过程

#include <iostream>
using namespace std;

class Student {
public:
	Student() {
		cout << "构造函数的调用" << endl;
	}
	~Student() {
		cout << "析构函数的调用" << endl;
	}
};

void func() {
	// 局部变量,函数执行完后自动释放内存
	Student s1;
}

int main() {

	func();
	// output:构造函数的调用
	//		  析构函数的调用

	system("pause");
	return 0;
}

构造函数的分类以及调用

构造函数的分类:有参构造函数和无参构造函数(按参数)普通构造函数和拷贝构造函数(按类型)

重点:理解拷贝构造函数的参数;如下构造函数的调用属于括号法

#include <iostream>
using namespace std;

class Student {
public:
	// 无参构造函数
	Student() {
		cout << "无参构造函数的调用" << endl;
	}
	// 有参构造函数
	Student(int a) { 
		cout << "有参构造函数的调用" << endl;
	}
	// 拷贝构造函数
	Student(const Student &s) {
		cout << "拷贝构造函数的调用" << endl;
	}
};

int main() {

	Student s1; // 无参构造函数的调用
	Student s2(2); // 有参构造函数的调用
	Student s3(s1); // 拷贝构造函数的调用

	system("pause");
	return 0;
}

构造函数的调用方式:括号法;显示法;隐式转换法

#include <iostream>
using namespace std;

class Student {
public:
	// 无参构造函数
	Student() {
		cout << "无参构造函数的调用" << endl;
	}
	// 有参构造函数
	Student(int a) { 
		cout << "有参构造函数的调用" << endl;
	}
	// 拷贝构造函数
	Student(const Student &s) {
		cout << "拷贝构造函数的调用" << endl;
	}
};

int main() {

	// 1. 括号法
	Student s1; // 无参构造函数的调用
	Student s2(2); // 有参构造函数的调用
	Student s3(s1); // 拷贝构造函数的调用

	// 2. 显示法
	Student s4; // 无参构造函数的调用
	Student s5 = Student(2); // 有参构造函数的调用
	Student s6 = Student(s4); // 拷贝构造函数的调用

	// 3. 隐式转换法
	Student s7; // 无参构造函数的调用
	Student s8 = 2; // 有参构造函数的调用
	Student s9 = s7; // 拷贝构造函数的调用

	system("pause");
	return 0;
}

拷贝构造函数调用时机

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

第一种:使用一个已经初始化完毕的对象来初始化一个新对象

#include <iostream>
using namespace std;

class Student {
public:
	// 构造函数
	// 无参构造函数
	Student() {
		cout << "无参构造函数的调用" << endl;
	}
	// 有参构造函数
	Student(int a) { 
		cout << "有参构造函数的调用" << endl;
	}
	// 拷贝构造函数
	Student(const Student &s) {
		cout << "拷贝构造函数的调用" << endl;
	}

	// 析构函数
	~Student() {
		cout << "析构函数的调用" << endl;
	}
};

void func() {

	// 实例化对象 s1
	Student s1;
	// 第一种情况:使用已经初始化好的对象来初始化新对象(此过程调用拷贝构造函数)
	Student s2(s1);
}

int main() {

	func();
	// outputs:无参构造函数的调用
	//		   拷贝构造函数的调用
	//	       析构函数的调用
	//	       析构函数的调用

	system("pause");
	return 0;
}

第二种:值传递的方式给函数参数传值

#include <iostream>
using namespace std;

class Student {
public:
	// 构造函数
	// 无参构造函数
	Student() {
		cout << "无参构造函数的调用" << endl;
	}
	// 有参构造函数
	Student(int id) { 
		cout << "有参构造函数的调用" << endl;
	}
	// 拷贝构造函数
	Student(const Student &s) {
		m_id = s.m_id;
		cout << "拷贝构造函数的调用" << endl;
	}

	// 析构函数
	~Student() {
		cout << "析构函数的调用" << endl;
	}

public:
	// 设置 id 的函数
	void setId(int id) {
		m_id = id;
	}
	// 打印 id 的函数
	int getId() {
		return m_id;
	}

private:
	int m_id;
};

void work(Student s) {
	// 打印 s 的 id 
	cout << s.getId() << endl;
}

void func() {

	// 实例化对象 s1
	Student s1;
	s1.setId(1); // id 为 1
	cout << s1.getId() << endl;
	// 将对象 s1 作为值给函数 work 传参
	work(s1);
}

int main() {

	func();
	// outputs:有参构造函数的调用
	//		   1
	//	       拷贝构造函数的调用
	//	       1
	//	       析构函数的调用
	//	       析构函数的调用

	system("pause");
	return 0;
}

第三种:以值方式返回局部对象

#include <iostream>
using namespace std;

class Student {
public:
	// 构造函数
	// 无参构造函数
	Student() {
		cout << "无参构造函数的调用" << endl;
	}
	// 有参构造函数
	Student(int id) { 
		cout << "有参构造函数的调用" << endl;
	}
	// 拷贝构造函数
	Student(const Student &s) {
		m_id = s.m_id;
		cout << "拷贝构造函数的调用" << endl;
	}

	// 析构函数
	~Student() {
		cout << "析构函数的调用" << endl;
	}

public:
	// 设置 id 的函数
	void setId(int id) {
		m_id = id;
	}
	// 打印 id 的函数
	int getId() {
		return m_id;
	}

private:
	int m_id;
};

// 以值方式返回局部对象
Student work() {
	Student s;
	s.setId(2); // id 为 2
	cout << s.getId() << endl;
	return s;
}

void func() {
	Student s1 = work(); // 调用拷贝构造函数
	cout << s1.getId() << endl; 
}

int main() {

	func();
	// outputs:无参构造函数的调用
	//		   2
	//	       拷贝构造函数的调用
	//	       析构函数的调用
	//		   2
	//		   析构函数的调用

	system("pause");
	return 0;
}

构造函数调用规则

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

第一种:默认构造函数(无参且函数体为空)

class Student {
public:
	Student() {
		pass
	}

第二种:默认析构函数(无参且函数体为空)

class Student {
public:
	~Student() {
		pass
	}

第三种:默认拷贝构造函数(对属性进行浅拷贝)

class Student {
public:
	Student(const Student &s) {
		属性值 = s.属性值;
	}

构造函数调用规则如下:

规则 1:如果用户自己定义了有参构造函数,C++ 不再提供默认无参构造函数,但是会提供默认拷贝函数

#include <iostream>
using namespace std;

class Student {
public:
	// 有参构造函数
	Student(int id) { 
		m_id = id;
		cout << "有参构造函数的调用" << endl;
	}

public:
	// 打印 id 的函数
	int getId() {
		return m_id;
	}

private:
	int m_id;
};

void func() {
	// 调用有参构造函数成功!
	Student s1(1); // id 为 1
	// 调用无参构造函数失败!
	// Student s2; // Error!
	// 调用拷贝构造函数成功!
	Student s3(s1);
	cout << s3.getId() << endl;
}

int main() {

	func();
	// outputs:有参构造函数的调用
	//		   1

	system("pause");
	return 0;
}

规则 2:如果用户定义了拷贝构造函数,C++ 不会再提供其它构造函数

#include <iostream>
using namespace std;

class Student {
public:
	// 拷贝构造函数
	Student(const Student &s) { 
		m_id = s.getId();
		cout << "有参构造函数的调用" << endl;
	}

public:
	// 设置 id 的函数
	void setId(int id) {
		m_id = id;
	}
	// 打印 id 的函数
	int getId() {
		return m_id;
	}

private:
	int m_id;
};

void func() {
	// 由于没有无参构造和有参构造函数,故下面代码出错!
	// Student s1; // Error!
	// Student s2(1); // Error!
}

int main() {

	func();
	// outputs:有参构造函数的调用
	//		   1

	system("pause");
	return 0;
}

深拷贝和浅拷贝

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

#include <iostream>
using namespace std;

class Student {
public:
	// 构造函数(默认有无参和拷贝 -- 简单的值拷贝)
	// 析构函数
	~Student() {
		// 将堆区开辟的内存清空
		if (m_height != NULL) {
			delete m_height;
			m_height = NULL;
		}
	}
public:
	// 设置 id,height 的函数
	void setIdAndHeight(int id, int height) {
		m_height = new int(height); // 从堆区开辟内存,返回值为指针
		m_id = id;
	}
	// 获取 m_height
	int* getHeight() {
		return m_height;
	}
private:
	int m_id;
	int *m_height;
};

// 在函数调用结束后,s1 和 s2 都调用了析构函数,都进行了 delete m_height;
// 然而对同一段内存空间进行二次清空是非法的
void func() {
	Student s1;
	s1.setIdAndHeight(1, 178);
	cout << *s1.getHeight() << endl; // 178
	// 拷贝构造函数创建一个新对象
	// Student s2(s1); // 简单的拷贝 -- m_height 的地址也是一样的
}

int main() {

	func();
	// outputs:有参构造函数的调用
	//		   1

	system("pause");
	return 0;
}

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

#include <iostream>
using namespace std;

class Student {
public:
	// 构造函数(默认有无参和拷贝 -- 深拷贝)
	// 重写拷贝构造函数
	Student() {
		cout << "无参构造函数调用" << endl;
	}
	Student(const Student &s) {
		m_id = s.m_id;
		m_height = new int(*s.m_height); // 重新申请内存空间
	}
	// 析构函数
	~Student() {
		// 将堆区开辟的内存清空
		if (m_height != NULL) {
			delete m_height;
			m_height = NULL;
		}
	}
public:
	// 设置 id,height 的函数
	void setIdAndHeight(int id, int height) {
		m_height = new int(height); // 从堆区开辟内存,返回值为指针
		m_id = id;
	}
	// 获取 m_height
	int* getHeight() {
		return m_height;
	}
private:
	int m_id;
	int *m_height;
};

// 在函数调用结束后,s1 和 s2 都调用了析构函数,都进行了 delete m_height;
// 然而对同一段内存空间进行二次清空是非法的
void func() {
	Student s1;
	s1.setIdAndHeight(1, 178);
	cout << (int)s1.getHeight() << endl; 
	// 拷贝构造函数创建一个新对象
	Student s2(s1); // 深拷贝 -- 内存空间不一样
	cout << (int)s2.getHeight() << endl; 
}

int main() {

	func();
	// outputs:无参构造函数的调用
	//		   7734624
	//		   7793792

	system("pause");
	return 0;
}

初始化列表

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

#include <iostream>
using namespace std;

class Student {
public:
	// 构造函数用于初始化列表
	Student(int id, int score) :m_id(id), m_score(score) {
		cout << "有参构造函数调用" << endl;
	}
	// 析构函数
	~Student() {
		cout << "析构函数调用" << endl;
	}

public:
	void printMessage() {
		cout << "学号:" << m_id << " " << "分数:" << m_score << endl;
	}

private:
	int m_id;
	int m_score;
};

void func() {
	Student s1(1,100);
	s1.printMessage();
}

int main() {

	func();
	// outputs:无参构造函数调用
	//         学号:1 分数:100
	//	       析构函数调用

	system("pause");
	return 0;
}

在本质上和如下初始化方法一致

#include <iostream>
using namespace std;

class Student {
public:
	// 构造函数用于初始化列表
	Student(int id, int score) {
		m_id = id;
		m_score = score;
		cout << "有参构造函数调用" << endl;
	}
	// 析构函数
	~Student() {
		cout << "析构函数调用" << endl;
	}

public:
	void printMessage() {
		cout << "学号:" << m_id << " " << "分数:" << m_score << endl;
	}

private:
	int m_id;
	int m_score;
};

void func() {
	Student s1(1,100);
	s1.printMessage();
}

int main() {

	func();
	// outputs:无参构造函数调用
	//         学号:1 分数:100
	//	       析构函数调用

	system("pause");
	return 0;
}

类对象作为类成员

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

class A {}	

class B {
	A a;
}

B 类中有 a 作为成员,a 为对象成员


实例:学生类和手机类(手机被学生所拥有)

#include <iostream>
using namespace std;
#include <string>

class Phone {
public:
	Phone(string name){
		p_name = name;
	}
	// 获取手机信息
	string getPhone() {
		return p_name;
	}
private:
	string p_name;
};

class Student {
public:
	// 构造函数用于初始化列表
	Student(string name, string phone) :m_name(name), m_phone(phone) {
		cout << "有参构造函数调用" << endl;
	}
	// 析构函数
	~Student() {
		cout << "析构函数调用" << endl;
	}
	// 打印学生的信息
	void printMessage() {
		cout << m_name << "同学有" << m_phone.getPhone() << endl;
	}

private:
	string m_name;
	Phone m_phone;
};

void func() {
	// 初始化学生对象
	Student s("苏苏", "HuaWei");
	s.printMessage();
}

int main() {

	func();
	// outputs:有参构造函数调用
	//         苏苏同学有HuaWei
	//         析构函数

	system("pause");
	return 0;
}

静态成员和静态成员函数

静态成员就是在成员变量和成员函数前加一个 static

静态成员变量特点:所有对象共享同一份数据;在编译阶段分配数据;类内声明,类外初始化

静态成员函数特点:所有对象共享同一份数据;静态成员函数只能访问静态成员变量


重点 1:验证所有对象共享静态成员变量

#include <iostream>
using namespace std;
#include <string>

class Student {
public:
	// 类内声明
	static string m_name;
};
// 类外初始化
string Student::m_name = "苏苏";

void func() {
	Student s1;
	cout << s1.m_name << endl;
	// 创建一个新对象 s2,来验证所有的对象都共享这个数据
	Student s2;
	s2.m_name = "娜娜";
	cout << s1.m_name << endl;
}

int main() {

	func();
	// outputs:苏苏
	//         娜娜
	

	system("pause");
	return 0;
}

重点 2:验证静态成员函数只能访问静态成员变量

#include <iostream>
using namespace std;
#include <string>

class Student {
public:
	// 类内声明
	static string m_name;

	// 静态成员函数
	static void changeMessage() {
		string name;
		cin >> name;
		m_name = name;
	}
};
// 类外初始化
string Student::m_name = "苏苏";

void func() {
	Student s1;
	cout << s1.m_name << endl;
	s1.changeMessage();
	cout << s1.m_name << endl;
}

int main() {

	func();
	// outputs:苏苏
	//         柯柯
	
	system("pause");
	return 0;
}

重点 3:静态成员变量和函数都可以由类名直接访问

#include <iostream>
using namespace std;
#include <string>

class Student {
public:
	// 类内声明
	static string m_name;

	// 静态成员函数
	static void changeMessage() {
		string name;
		cin >> name;
		m_name = name;
	}
};
// 类外初始化
string Student::m_name = "苏苏";

void func() {
	cout << Student::m_name << endl;
	Student::changeMessage();
	cout << Student::m_name << endl;
}

int main() {

	func();
	// outputs:苏苏
	//         柯柯
	
	system("pause");
	return 0;
}

重点 4:类外访问不到私有静态成员变量(同理也访问不到静态成员函数)

#include <iostream>
using namespace std;
#include <string>

class Student {
public:
	// 类内声明
	static string m_name;

	// 静态成员函数
	static void changeMessage() {
		string name;
		cin >> name;
		m_name = name;
	}

private:
	int m_id;
};
// 类外初始化
string Student::m_name = "苏苏";
int Student::m_id = 1;

void func() {
	cout << Student::m_name << endl;
	Student::changeMessage();
	cout << Student::m_name << endl;
	// 访问私有静态成员变量
	// cout << Student::m_id << endl; // Error!
}

int main() {

	func();
	// outputs:苏苏
	//         柯柯
	
	system("pause");
	return 0;
}

成员变量和成员函数存储位置

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

重点问题:空对象占用内存为 1 的原因是为了区分空对象所占内存的位置

#include <iostream>
using namespace std;
#include <string>

// 空类
class Student1 {

};

// 非静态成员变量
class Student2 {
	int m_id;
};

// 静态成员变量
class Student3 {
	static int m_id;
};

// 静态成员函数
class Student4 {
	static int func() {

	}
};

// 非静态成员函数
class Student5 {
	int func() {

	}
};

void func() {
	cout << "空 Student1 占用空间为:" << sizeof(Student1) << endl;
	cout << "Student2 占用空间为:" << sizeof(Student2) << endl;
	cout << "Student3 占用空间为:" << sizeof(Student3) << endl;
	cout << "Student4 占用空间为:" << sizeof(Student4) << endl;
	cout << "Student5 占用空间为:" << sizeof(Student5) << endl;
}

int main() {

	func();
	// outputs:空 Student1 占用空间为:1
	//         空 Student1 占用空间为:4
	//         空 Student1 占用空间为:1
	//         空 Student1 占用空间为:1
	//         空 Student1 占用空间为:1
	
	system("pause");
	return 0;
}

this 指针概念

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


案例 1:区分形参和成员变量名

#include <iostream>
using namespace std;
#include <string>

// 空类
class Student {
public:
	void setId(int m_id) {
		this->m_id = m_id; // this->m_id 表示调用该函数的对象,也就是 s1->m_id = id;
	}
	int getId() {
		return m_id;
	}

private:
	int m_id;
};

void func() {
	Student s1;
	s1.setId(1);
	cout << s1.getId() << endl;
}

int main() {

	func();
	// output:1

	system("pause");
	return 0;
}

案例 2:对象年龄的加法问题

#include <iostream>
using namespace std;
#include <string>

// 空类
class Student {
public:
	void setYear(int m_age) {
		this->m_age = m_age; 
	}
	int getYear() {
		return m_age;
	}
	Student& addyear(Student &s) {
		this->m_age += s.getYear();
		return *this;
	}

private:
	int m_age;
};

void func() {
	Student s1, s2;
	s1.setYear(10);
	cout << s1.getYear() << endl; // 10
	s2.setYear(5);
	s1.addyear(s2);
	cout << s1.getYear() << endl; // 15
	s1.addyear(s2).addyear(s2).addyear(s2);
	cout << s1.getYear() << endl; // 30
}

int main() {

	func();

	system("pause");
	return 0;
}

空指针访问成员函数

C++ 中的空指针也可以调用成员函数,但是也得注意函数体内部是否用到了 this 指针

#include <iostream>
using namespace std;

class Student {
public:
	void printMessage() {
		cout << "你是猪" << endl;
	}
	void changeMessage() {
		this->m_id = 1; // 很明显 this->m_id 即 s->m_id 而 s 指向的为空
	}

private:
	int m_id;
};

void func() {
	// 初始化学生对象,使其指向空内存
	Student *s = NULL;
	s->printMessage(); // 你是猪
	// s->changeMessage(); // Error!
}

int main() {

	func();

	system("pause");
	return 0;
}

const 修饰成员函数

常函数

成员函数后加 const 我们称之为常函数

常函数内不可修改成员属性

成员属性声明时若加了关键字 mutable,在常函数中就可以修改了

#include <iostream>
using namespace std;

class Student {
public:
	Student(int id) {
		m_id = id;
	}
	// 常函数不能修改成员属性
	//void changeId() const {
	//	int id;
	//	cin >> id;
	//	m_id = id;
	//}
	// 常函数不能修改成员属性,但是如果在属性前面加上关键字 mutable,那么就可以在常函数中修改了
	void changeScore() const {
		int score;
		cin >> score;
		m_score = score;
	}
	void printMessage() {
		cout << "学号" << m_id << "的成绩为:" << m_score << endl;
	}
private:
	int m_id;
	mutable int m_score;
};

void func() {
	Student s1(1); // id 为 1
	// s1.changeId(); // Error!
	s1.changeScore();
	s1.printMessage(); // 学号1的成绩为:100
}

int main() {

	func();

	system("pause");
	return 0;
}

常对象

声明对象前加 const 我们称之为常对象

常对象只能调用常函数和用关键字 mutable 修饰的成员属性

#include <iostream>
using namespace std;

class Student {
public:
	Student(int id) {
		m_id = id;
	}
	void changeScore() const {
		int score;
		cin >> score;
		m_score = score;
	}
	void printMessage() {
		cout << "学号" << m_id << "的成绩为:" << m_score << endl;
	}
private:
	int m_id;
	mutable int m_score;
};

void func() {
	// 常对象调用常成员函数
	const Student s1(1); // id 为 1
	s1.changeScore();

	// 常对象调用非常成员函数
	// s1.printMessage(); // Error!
}

int main() {

	func();

	system("pause");
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是我来晚了!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值