C++类和对象入门(二)
目录
目录
一、类的默认成员函数
二.构造函数
2.1函数名与类名相同
2.2构造函数无返回值
2.3 对象实例化时系统会自动调用
2.4支持函数重载
2.5默认构造函数
2.6无参构造函数与全缺省构造函数与默认构造函数
三、析构函数
3.1析构函数名称
3.2无参数和返回值
3.3一个类只能有一个析构函数
3.4自动调用
3.5显式写析构函数
3.6可以不写的情况
3.7析构函数执行的顺序
四、拷贝构造函数
4.1定义
4.2本质
4.3第一个参数
4.4自定义类型对象的拷贝
4.5若没有显式地写,编译器会自动生成
4.6拷贝函数的传值返回
结语
前言,本文所介绍的知识建立在上文:《C++类和对象入门》
下面我们来继续介绍C++类和对象的进一步讲解和介绍。
一、类的默认成员函数
在C++中存在默认成员函数,默认成员函数是指在用户没有显写函数的时候,编译器在C++中会自动生成的函数,一般会有六个函数默认生成,如下图,理解这些函数的行为和作用是理解C++类机制的基础。
本文将详细介绍3个默认成员函数:构造函数、析构函数和拷贝函数。
二.构造函数
构造函数是用于初始化对象的特殊成员函数,它的主要任务是初始化对象的域元变量,并不为对象分配内存,确保对象在创建的时候是有效的。
2.1函数名与类名相同
C++语法要求,构造函数的名称要和类名相同,这能让编译器识别这个是构造函数而不是普通成员函数。
class MyClass {
public:
MyClass() { /* 构造函数体 */ }
};
2.2构造函数无返回值
构造函数无返回值,甚至不需要void。它的作用仅仅是去初始化对象,不需要任何返回值。
class MyClass {
public:
MyClass() { /* 构造函数体 */ }
};
2.3 对象实例化时系统会自动调用
构造函数在对象实例化时自动调用。开发者不需要显式调用构造函数,编译器会在对象创建时自动执行它。
2.4支持函数重载
构造函数支持函数重载,不同的函数可以使用同一个函数名称,但是需要保证函数参数列表是不同的,例如:
class MyClass {
public:
MyClass() { /* 无参构造函数 */ }
MyClass(int x) { /* 带参构造函数 */ }
};
2.5默认构造函数
默认构造函数提供了一个基本的初始化方式。如果用户定义了其他形式的构造函数(如带参数的),编译器认为用户不再需要默认构造函数,因此不会自动生成。
2.6无参构造函数与全缺省构造函数与默认构造函数
首先声明,这三种函数不能同时存在,三种函数作用相似,初始化对象不需要提供显式参数的函数,但这三个函数任意其一可以和带参数的函数一起存在(函数重载)。
例子:
#include <iostream>
using namespace std;
class MyClass {
public:
// 全缺省构造函数
MyClass(int x = 10, int y = 20) {
//函数体
}
// 普通带参构造函数
MyClass(double z) {
//函数体
}
};
int main() {
MyClass obj1;
MyClass obj2(10);
MyClass obj3(3.14);
return 0;
}
三、析构函数
析构函数和构造函数的作用相反,它用于在函数的生命周期结束的时候释放资源。
值得说的是,析构函数会在对象销毁时自动调用,以完成对象中资源的清理工作。这一特性使得C++能够有效地管理内存和其他资源,防止资源泄漏。
3.1析构函数名称
析构函数名和类名相同,在前面加上一个 ~
class MyClass {
public:
~MyClass() {
// 析构函数体
}
};
3.2无参数和返回值
析构函数是用来清理对象的资源的,不需要参数和返回值。
class MyClass {
public:
~MyClass() {
// 无参数,无返回值
}
};
3.3一个类只能有一个析构函数
C++规定,一个类只能有一个析构函数,因为一个对象只能在生命周期结束时被销毁一次。
class MyClass {
public:
~MyClass() {
// 只能有一个析构函数
}
};
3.4自动调用
当一个对象的生命周期结束(如对象超出作用域或显式删除对象)时,系统会自动调用析构函数来清理资源。
如果显式定义了析构函数,对于自定义类型的成员变量,它们的析构函数也会被自动调用。
class MyClass {
private:
std::string _name; // 自定义类型成员
public:
~MyClass() {
// 自定义类型的成员变量会自动调用其析构函数
}
};
3.5显式写析构函数
如果显式定义了析构函数,C++会确保自定义类型的成员变量,在生命周期结束的时候,也会自动调用他的析构函数。
class MYclass
{
private:
std::string_name;
public:
~MYclass()
{}
}
3.6可以不写的情况
如果类里没有需要动态分配的资源或者需要手动释放的资源,可以不需要写析构函数,可以使用编译器的默认析构函数。例如:
class Myclass
{
private:
int_value;//没有需要动态分配的资源
}
3.7析构函数执行的顺序
C++规定,先定义的函数后析构。
这一规则保证了对象按照先进后出的顺序进行销毁,这符合栈的逻辑。
class Myclass
{
public:
~Myclass()
{}
};
int main()
{
Myclass odj 1;
Myclass obj 2;//这里2回比1先销毁
return 0;
}
下面,将用两个具体的代码实例帮助大家理解构造函数和析构函数在C++中的性质和作用。
实例一:利用两个栈实现队列
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack {
public:
Stack(int n = 4) {
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (_a == nullptr) {
perror("malloc申请空间失败");
return;
}
_capacity = n;
_top = 0;
}
~Stack() { // 自定义析构函数,释放动态分配的内存
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
// 两个Stack实现队列
class MyQueue {
public:
MyQueue() : pushst(), popst() {}
// 默认析构函数自动调用两个Stack成员的析构函数
// 显式定义的析构函数,也会自动调用Stack成员的析构函数
/*~MyQueue() {}*/
private:
Stack pushst;
Stack popst;
};
int main() {
Stack st;
MyQueue mq; // MyQueue的析构函数会自动调用pushst和popst的析构函数
return 0;
}
实例二:用C++解决括号匹配
#include<iostream>
using namespace std;
bool isValid(const char* s) {
Stack st;
while (*s) {
if (*s == '[' || *s == '(' || *s == '{') {
st.Push(*s);
} else {
if (st.Empty()) {
return false;
}
char top = st.Top();
st.Pop();
if ((*s == ']' && top != '[') ||
(*s == '}' && top != '{') ||
(*s == ')' && top != '(')) {
return false;
}
}
++s;
}
return st.Empty(); // 确保所有括号都匹配
}
int main() {
cout << isValid("[()][]") << endl; // 输出1(true)
cout << isValid("[(])[]") << endl; // 输出0(false)
return 0;
}
四、拷贝构造函数
4.1定义
拷贝构造是一种构造函数,用于通过一个已经存在的对象来构造一个新的对象。
在C++中,如果一个函数的第一个参数是自身类型的引用,那么这个函数就是拷贝构造函数。
4.2本质
拷贝构造函数本质就是原函数的一个函数重载。
因为定义中讲,如果一个函数的第一个参数是自身类型的引用,那么这个函数就是拷贝构造函数。那么这两个函数的函数名必然相同,只不过参数列表不同,一个是函数本身的参数列表,另一个是同类对象的引用。
4.3第一个参数
注意!!拷贝构造函数的第一个参数必须调用类类型对象的引用,不能传值,否则就会出现无限调用拷贝构造,导致编译错误。如下图:
4.4自定义类型对象的拷贝
在C++中,无论是传值,还是从函数内部返回一个对象,C++都调用了拷贝函数来创建新的对象。
4.5若没有显式地写,编译器会自动生成
如果没有显式的写拷贝函数,编译器会自动生成一个拷贝函数,对函数的内置类型进行拷贝,不过注意,这是浅拷贝,浅拷贝指的是例如简单变量,数组等,可以直接拷贝一份给别的地方用,但是如果遇到例如,栈,这一类需要动态分配资源的,浅拷贝会导致例如两个资源共用一个空间的矛盾产生,这时需要自定义函数来实现深拷贝。
4.6拷贝函数的传值返回
在C++中,通过值返回对象时,编译器会调用拷贝构造函数来创建返回值的副本。如果通过引用返回对象,则没有拷贝发生。然而,引用返回需要确保返回的对象在函数结束后仍然存在,否则会导致悬空引用。例如:
MyClass ReturnByValue() {
MyClass temp(10);
return temp; // 调用拷贝构造函数,返回对象副本
}
MyClass& ReturnByReference() {
static MyClass temp(10); // 使用static,确保返回的引用有效
return temp; // 返回引用,不调用拷贝构造函数
}
int main() {
MyClass obj1 = ReturnByValue(); // 调用拷贝构造函数
MyClass& obj2 = ReturnByReference(); // 不调用拷贝构造函数
return 0;
}
结语
至此,本文介绍的关于C++入门的部分知识正式结束,日后我会更新更多关于C++的基础入门知识,如果本文能帮助到阅读文章的你,就请点赞转发收藏吧,您的支持也是我继续学习和更新的动力,感谢支持!!