本地网站建设需要什么,app下载安装免费下载t,国内最新的新闻,杭州新闻最新消息新闻一:类的六个默认成员函数
如果一个类中什么成员都没有#xff0c;简称为空类。空类中并不是什么都没有#xff0c;任何类在什么都不写时#xff0c;编译器会自动生成以下6个默认成员函数。默认成员函数#xff1a;用户没有显式实现#xff0c;编译器会生成的成员函数称为…一:类的六个默认成员函数
如果一个类中什么成员都没有简称为空类。空类中并不是什么都没有任何类在什么都不写时编译器会自动生成以下6个默认成员函数。默认成员函数用户没有显式实现编译器会生成的成员函数称为默认成员函数 二:构造函数
引出: 在类中我们可以自己定义出它的初始化函数 Init (),但是每次需要调用这个函数,有点麻烦 那有没有什么方法可以让对象在创建时就可以完成初始化呢? 答:构造函数就可以解决这个问题 构造函数概念: 构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用以保证每个数据成员都有 一个合适的初始值并且在对象整个生命周期内只调用一次,需要注意的是,构造函数虽然名称叫构造但是构造函数的主要任务并不是开空间创建对象而是初始化对象 构造函数特性: 构造函数有以下特征: 1. 函数名与类名相同。2. 无返回值。3. 对象实例化时编译器自动调用对应的构造函数。4. 构造函数可以重载 5. 如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成 情景一: 类内没有定义构造函数,编译器会自动生成一个无参构造函数 class Date
{
private:int _year;int _month;int _day;
};
void TestDate()
{Date d1; // 类没有显式定义构造函数,编译器会自动生成一个无参构造函数
} 情景二: 类内定义了自己的构造函数,则编译器就不会生成无参构造函数了,对象的初始化只能使用自己定义的构造函数 6. 无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。注意无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为 是默认构造函数。 class Date
{
public:// 无参构造函数Date(){_year 2024;_month 10;_day 1;}//带参构造函数Date(int year, int month, int day){_year year;_month month;_day day;}//缺省参数形式Date(int year2024, int month10, int day1){_year year;_month month;_day day;}
private:int _year;int _month;int _day;
};
void TestDate()
{Date d1; // 调用无参构造函数
} 如代码所示,由于同时定义出了无参构造函数和全缺省构造函数,虽然他们满足函数重载,语法没有问题,但他们两个都属于默认构造函数,当创建对象时,编译器会不知道调用哪个而导致编译失败 对编译器自动生成的构造函数的理解 不实现构造函数的情况下编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用d对象调用了编译器生成的默认构造函数但是d对象_year/_month/_day依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用 C把类型分成内置类型(基本类型)和自定义类型。 内置类型就是语言提供的数据类型如int/char...自定义类型就是我们使用class/struct/union等自己定义的类型 编译器自动生成的默认构造函数对内置类型确实不做处理,其主要功能是管理类中的自定义类型。当类中存在自定义类型时,默认构造函数会调用该自定义类型的默认构造函数实现初始化 注意C11 中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在 类中声明时可以给默认值。 三:析构函数 析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数完成对象中资源的清理工作 特性: 析构函数是特殊的成员函数其特征如下 析构函数名是在类名前加上字符 ~无参数无返回值类型 一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。注意析构函数不能重载 对象生命周期结束时C编译系统系统自动调用析构函数如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数比如Date类有资源申请时一定要写否则会造成资源泄漏比如Stack类。 显示定义析构函数 在对象声明周期结束时,程序会自动调用析构函数完成资源清理 默认生成的析构函数 1.析构函数不对内置类型做处理,因为内置类型变量的内存会随着栈帧的销毁而被回收 2.若类中存在自定义类型,则编译器会自动调用自定义类类型的析构函数 Date类中并没有显式定义析构函数。当需要销毁对象d的时候编译器会自动生成一个析构函数用于对Date类中的动态资源的清理如果有自定义类型则会去调用自定义类型自己的析构函数。 总之:创建哪个类的对象则调用该类的构造函数销毁那个类的对象则调用该类的析构函数 四:拷贝构造函数
拷贝构造函数:只有单个形参该形参是对本类类型对象的引用(一般常用const修饰)在用已存 在的类类型对象创建新对象时由编译器自动调用,用来创建一个与已存在对象一某一样的新对象 特性: 拷贝构造函数是构造函数的一个重载形式。拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用 class Date
{
public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}// Date(const Date d) // 正确写法Date(const Date d) // 错误写法编译报错会引发无穷递归{_year d._year;_month d._month;_day d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);return 0;
} 对于值传参的函数,形参是实参的一份拷贝,在C中,这个拷贝需要调用拷贝构造函数,如果拷贝构造函数的形参没有加引用的话,就会导致无穷递归调用构造拷贝函数 3. 若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。 ps.若类成员中存在自定义类型则会调用该类型自己的拷贝构造函数来拷贝这个类型 4.问题来了: 如果一个类没有显式定义自己的拷贝构造函数,编译器会自动生成一个拷贝构造函数,就算类中存在自定义类型,编译器也会调用该自定义类型自己的拷贝构造函数,那是不是就可以不显式定义拷贝构造函数了?看如下情景: // 这里会发现下面的程序会崩溃掉这里就需要我们以后讲的深拷贝去解决。
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;
} [崩溃原因]: 创建对象d1时对d1._array动态开辟了一部分空间,而拷贝构造d2对象时,由于编译器自动生成的拷贝构造函数是按字节完成拷贝的浅拷贝,则d2._array指针指向的也是d1._array所指的空间,这样当d2对象生命周期结束时,会调用析构函数将这个空间回收,那么d1所指空间也被回收了,当d1的生命周期结束时也会调用析构函数,同一块空间被free两次就会导致程序崩溃 类中如果没有涉及资源申请时拷贝构造函数是否写都可以一旦涉及到资源申请时则拷贝构造函数是一定要写的否则就是浅拷贝。 正确写法: 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;}//拷贝构造函数Stack(const Stack d){DataType* tmp (DataType*)malloc(sizeof(DataType) * d._capacity);if (tmp nullptr){perror(malloc);return;}memcpy(tmp, d._array, sizeof(DataType) * d._size);_array tmp;_size d._size;_capacity d._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;
}; 5. 拷贝构造函数典型调用场景 使用已存在对象创建新对象函数参数类型为类类型对象函数返回值类型为类类型对象 建议: 为了提高程序效率一般对象传参时尽量使用引用类型返回时根据实际场景能用引用尽量使用引用。 五:运算符重载 概念: C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。 函数名字为关键字operator后面接需要重载的运算符符号。函数原型返回值类型 operator操作符(参数列表) 返回值类型 operator 运算符(形参表)
{....
} 注意 不能通过连接其他符号来创建新的操作符比如operator重载操作符必须有一个类类型参数用于内置类型的运算符其含义不能改变例如内置的整型不 能改变其含义作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this.* :: sizeof ?: . 注意以上5个运算符不能重载。 实现举例: 实现1: 将重载函数定义在全局(成员变量要定义为public,在类外才能使用) class Date
{
public://构造函数Date(int year 2024, int month 2, int day 2){_year year;_month month;_day day;}//注意此时成员变量是公有的int _year;int _month;int _day;
};
bool operator(const Date d1,const Date d2)
{return d1._year d2._year d1._month d2._day d1._day d2._day;
}
int main()
{Date d1(2024, 8, 14);Date d2(2024, 8, 1);//调用运算符重载函数cout operator(d1,d2) endl;//简写cout (d1 d2) endl;return 0;
} 实现2:将重载函数定义为成员函数 class Date
{
public://构造函数Date(int year 2024, int month 2, int day 2){_year year;_month month;_day day;}bool operator(const Date d){return _year d._year _month d._day _day d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 8, 14);Date d2(2024, 8, 1);//调用运算符重载函数cout d1.operator(d2) endl;//简写cout (d1 d2) endl;return 0;
} 六:赋值运算符重载
赋值重载的作用是将一个已经存在的对象赋值给另一个已经存在的对象(要区分拷贝构造函数) 1. 赋值运算符重载格式 虽然上述赋值重载的写法可以完成赋值的作用,但是它的功能却不完善 对于普通类型可以进行连续赋值 ijk10,但是按刚刚的写法却不能实现对象的连续赋值,要明白怎么怎么解决要先了解赋值语句的返回值: 例如: ijk10 编译器会先把10赋值给k,然后返回k的值赋值给j,再返回j的值赋值给i,也就是赋值语句会返回左操作数 所以要想实现连续赋值我们需要将函数的返回值定义为类的类型 总结: 参数类型const T传递引用可以提高传参效率返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值检测是否自己给自己赋值返回*this 要复合连续赋值的含义 //赋值重载Date operator(const Date d){if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;} 2.赋值运算符只能重载成类的成员函数不能重载成全局函数 原因赋值运算符如果不显式实现编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载就和编译器在类中生成的默认赋值运算符重载冲突了故赋值 运算符重载只能是类的成员函数 3.用户没有显式实现时编译器会生成一个默认赋值运算符重载以值的方式逐字节拷贝。 注意内置类型成员变量是直接赋值的而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。 重点: 如果类中未涉及到资源管理赋值运算符是否实现都可以一旦涉及到资源管理则必须要实现(与拷贝构造原理类似) 七.const成员 将 const 修饰的“成员函数”称之为 const成员函数const修饰类成员函数实际修饰该成员函数隐含的this指针表明在该成员函数中不能对类的任何成员进行修改。 const对象不可以调用非const成员函数 非const对象可以调用const成员函数 const成员函数内不可以调用其它的非const成员函数 非const成员函数内可以调用其它的const成员函数 八:取地址及const取地址操作符重载 这两个默认成员函数一般不用重新定义 编译器默认会生成 class Date
{public :Date* operator(){return this ;}const Date* operator()const{return this ;}private :int _year ; // 年int _month ; // 月int _day ; // 日
}; 这两个运算符一般不需要重载使用编译器生成的默认取地址的重载即可只有特殊情况才需要重载比如想让别人获取到指定的内容