超详解C++类与对象(中)
目录
1. 构造函数
1.1. 定义
1.2. 注意
2.析构函数
2.1定义
2.2注意
3.拷贝构造函数
3..1. 定义
3.2. 注意
4.运算符重载
4.1. 定义
5. 赋值运算符重载
5.1. 定义
5.2. 注意
💓 博客主页:C-SDN花园GGbond
⏩ 文章专栏:玩转c++
1. 构造函数
1.1. 定义
构造函数是一个特殊的成员函数,名字与类名相同, 创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。其特点如下:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
下面是一个日期类的构造函数
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1(1,1,1);//自动调用d1.Print();return 0;
}
- 构造函数的功能就相当于我们之前书写的初始化函数,但由于其自动调用的特性,大大提升了代码的容错率。
1.2. 注意
1.无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。并且默认构造函数只能有一个(会出现调用歧义)
class Date
{
public:Date()//无参{_year = 1900;_month = 1;_day = 1;}Date(int year = 1900, int month = 1, int day = 1)//全缺省{_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d;//引起歧义return 0;
}
编译器不知道调用哪个构造函数,就会引起歧义。
2.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
class Date
{
public:/*Date(int year, int month, int day){_year = year;_month = month;_day = day;}*///编译器会自动生成一个无参的默认构造函数void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
3.编译器生成的默认构造函数只会对自定义类型(类)进行初始化(调用自定义类型的构造函数),内置类型(int,double…)不会进行初始化。
#include<iostream>
using namespace std;
class GGBond
{
public:GGBond(){cout << "GGBond" << endl;}
private:int _a;
};
class Date
{
public:void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:GGBond b;int _year;int _month;int _day;
};
int main()
{Date d;d.Print();return 0;
}
从上述实例观察,编译器自动生成的默认构造函数的确调用自定义类型的构造函数只对自定义类型进行初始化。
4.**C++11 中针对内置类型成员不初始化的缺陷,又进行了优化,即:内置类型成员变量在类中声明时可以给默认值。
class Date
{
public:void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year = 1;//默认值int _month = 1;//默认值int _day = 1;//默认值
};
int main()
{Date d;d.Print();return 0;
}
2.析构函数
2.1定义
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。其特点如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载(因为无参)
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
下面是一个日期类的析构函数:
class Date
{
public://构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}//析构函数~Date(){_year = _month = _day = 0;}
private:int _year;int _month;int _day;
};
析构函数就相当于C语言中的销毁函数,但由于其自动调用的特性,大大提升了代码的容错率
2.2注意
1.如果类中没有显式定义析构函数,则C++编译器会自动生成一个析构函数,一旦用户显式定义编译器将不再生成。
class Date
{
public://构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}//析构函数/*~Date(){_year = _month = _day = 0;}*///编译器会自动生成一个析构函数
private:int _year;int _month;int _day;
};
2.编译器生成的析构函数对成员变量的内置类型(int,double…)不会进行处理,对于成员变量的自定义类型调用其析构函数
#include<iostream>
using namespace std;
class GGBond
{
public:~GGBond(){cout << "~GGBond" << endl;}
private:int _a;
};
class Date
{
public://构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}//默认生成
private:GGBond b;int _year;int _month;int _day;
};
int main()
{Date d;d.Print();return 0;
}
3.因为指针类型也属于内置类型,所以默认成员在动态内存开辟内存后,必须显式写成析构函数。不能靠编译器默认生成。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 2){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array!=nullptr){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};
比如说上述代码,默认生成的析构函数并不会释放其内存,就可能造成内存泄漏。
3.拷贝构造函数
3..1. 定义
在C++编程中,拷贝构造函数是一个特殊的构造函数,用于从一个已存在的对象创建一个新的对象。如果你没有为类显式地定义一个拷贝构造函数,编译器会为你自动生成一个。这个由编译器自动生成的拷贝构造函数通常被称为默认拷贝构造函数(default copy constructor)
**拷贝构造函数:**只有单个形参,该形参是本类 类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。拷贝构造函数也是构造函数的一个重载形式。
示例
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d) {_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2024,2,2);Date d2(d1);//拷贝构造Date d3 = d1;//拷贝构造d1.Print();d2.Print();d3.Print();return 0;
}
3.2. 注意
1.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
Date(const Date d) //error{_year = d._year;_month = d._month;_day = d._day;}
2.若未显式定义,编译器会生构造函数(默认拷贝函数)。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
- 浅拷贝:默认拷贝构造函数通常执行浅拷贝,即复制对象的所有成员变量的值。如果成员变量是指针,那么指针本身的值(即地址)会被复制,但指向的数据不会被复制。这可能导致两个对象共享同一块内存,从而可能引发问题(如双重释放内存)。
- 非指针成员:对于非指针类型的成员变量,默认拷贝构造函数会正确复制其值。
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date(const Date& d)//{// _year = d._year;// _month = d._month;// _day = d._day;//}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 2, 2);Date d2(d1);//拷贝构造Date d3 = d1;//拷贝构造d1.Print();d2.Print();d3.Print();return 0;
}
编译器默认生成的拷贝构造函数是值拷贝,在某些场景下就会出错。比如说以下场景:
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);//默认拷贝构造return 0;
}
默认生成的拷贝构造只是进行值拷贝,对于size,capacity拷贝并不会出现问题,但是当s1的_array拷贝给s2的_array时,就会让s1与s2的同时指向同一片空间。而我们知道当对象的作用域结束时,会自动调用析构函数,同时对同一片空间析构两次,就会保错。所以当类中需要资源申请时,都需要手动写拷贝构造。
4.运算符重载
4.1. 定义
C++为了增强代码的可读性引入了运算符重载,运算符重载是具由运算符operator
定义有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。该函数能让我们自定义类型像内置类型一样使用-
,+
,*
,/
等运算符。
下面实现了简单判断日期是否相当的运算符重载:
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}int _year;int _month;int _day;
};
bool operator == (const Date&d1,const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
int main()
{Date d1(2024,1,1);Date d2(2024, 1, 1);if (d1 == d2)//也可以显示调用operator==(d1,d2);{cout << "日期相等" << endl;}else{cout << "日期不相等" << endl;}return 0;
}
当然我们也可以将运算符重载声明在类中。
#include<iostream>
using namespace std;
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}int _year;int _month;int _day;
};
int main()
{Date d1(2024, 1, 1);Date d2(2024, 1, 1);if (d1 == d2)//也可以显示调用operator==(d1,d2);{cout << "日期相等" << endl;}else{cout << "日期不相等" << endl;}return 0;
}
4..2. 注意
1.不能通过连接其他符号来创建新的操作符:比如operator@重载操作符必须有一个类类型参数
2.用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不 能改变其含义
3.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
4..* sizeof ? : :: . 注意以上5个运算符不能重载。
5. 赋值运算符重载
5.1. 定义
赋值运算符重载是将运算符 =
进行运算符重载。但是它相较于其他运算符重载有着自己独特(没有显示给,编译器在类中会给出)的特点。
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date& operator=(const Date& d)//赋值运算符重载{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};
- 参数类型:const T& ,传递引用可以提高传参效率。
- 返回值类型:T& ,返回引用可以提高返回的效率,支持连续赋值。
- 检测是否自己给自己赋值。
- 返回* this :要复合连续赋值的值。
5.2. 注意
1.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time& operator=(const Time& t){cout << "Time& operator=(const Time& t)" << endl;if (this != &t){_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 2024;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;Date d2;d1 = d2;return 0;
}
2.因为编译器默认生成默认赋值运算符重载的是值拷贝,在某些场景下就会出错。具体实例参考拷贝构造函数。
3.赋值运算符只能重载成类的成员函数不能重载成全局函数。
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
Date& operator=(Date& left, const Date& right)//error
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}
因为赋值运算符如果不显式实现,编译器会生成一个默认的赋值运算符重载。此时用户再在类外自己实现一个全局的赋值运算符重载,就会和编译器在类中生成的默认赋值运算符重载冲突。