怎么自己做购物网站,婴幼儿用品销售网站开发报告,wordpress文章全显示,vs 团队网站开发前言 紧接着上一篇文章#xff0c;接下来我们来认识下类的六大默认成员函数#xff0c;如下图。之所以叫他默认成员函数#xff0c;是因为即使我们不写#xff0c;编译器会默认帮我们写#xff0c;但只要我们自己显示的写了#xff0c;编译器就不会帮我们生成对应的成员函… 前言 紧接着上一篇文章接下来我们来认识下类的六大默认成员函数如下图。之所以叫他默认成员函数是因为即使我们不写编译器会默认帮我们写但只要我们自己显示的写了编译器就不会帮我们生成对应的成员函数。 类似于我们脱贫后就不会再有低保一样。 接下来我们一个一个接着看吧 构造函数
为什么要有构造函数 在讲构造函数之前我们在介绍下上篇文章的日期类实现如下。
class Date
{
public:void Init( int year, int month, int day){_day day;_month month;_year year;}void Print(){printf(%d-%d-%d\n, _year, _month, _day);}private:int _year;char t;int _month;int _day;
}; 我们要创建一个日期函数并且打印也十分容易如下代码。
int main()
{Date t;t.Init(2024, 4, 11);t.Print();return 0;
} 但是我们这里是正常的调用初始化函数假如我们忘记了初始化程序任然可以正常的运行但结果是随机值。如下代码结果。也许有读者观察到VS随机值打印几乎都是-858993460这是VS编译器做的初始化甚至有的编译器初始化为0每个编译器处理不同我们在这里就同一称之为随机值 我们在某些时候可能会忘记给相应的类初始化造成不可预料的后果初始化又是个令人容易忘记的事情那么有什么办法可以解决么 方法很简单我们身边就有一个永久记忆装置一但给他制定相应的规则他会严格执行丝毫不差的执行安排的任务。------------显而易见这个东西就是计算机我们可以把繁琐的任务甩给他专注于开发。
构造函数的定义 构造函数给人一种开辟空间的感觉但其实它的作用是初始化而不是开辟空间。我们可以把它当作一个特殊的函数他的语法如下。 1.要求函数名要和类名相同 2.没有返回值 3.对象实例化的时候编译器自动调用 4.可以重载
构造函数的使用 我们的本贾尼博士通过苦思冥想帮我们创造了方便的工具于是像上面的日期类便可以改写为如下实现。这样我们每次创建对象的时候对应构造函数就会自动的调用帮助我们减小记忆的负担。下面我们看个示例。
class Date
{
public:Date( int year, int month, int day){_day day;_month month;_year year;}void Print(){printf(%d-%d-%d\n, _year, _month, _day);}private:int _year;char t;int _month;int _day;
};
int main()
{Date t;t.Print();return 0;
} 但上面的代码是错误的我们没有显示的写构造函数的时候编译器会帮我们生成一个默认的构造函数这个函数没有参数如下。这个函数对于内置类型如int double float等不做任何的处理对于自定义类型classstruct调用其构造函数一直往下的递归下去我们明白所谓的自定义类型就是一些内置类型的集合所以可以认为默认的构造函数调用到可能什么都没干除非有自定义构造函数。
Date( )
{} 而一但我们自己定义了构造函数编译器就不会帮我们在创建构造函数了于是我们上面的Date t;相当于调用了无参的构造函数。但我们只定义了一个Date( int year, int month, int day)函数函数参数匹配不上自然就报错了。 对于这个问题我们有两个解决方案第一个是重载一个无参数的构造函数第二个就是用缺省参数我们用的最多的就是缺省参数。改动后如下。
Date( int year1, int month1, int day1)
{_day day;_month month;_year year;
} 因为构造函数可以重载我们甚至可以写出如下的构造函数。当然这个在日期类函数中没有什么实际的意义大家可以根据实际需要重构构造函数。
Date(float t)
{cout 恭喜你发现隐藏 endl;
} 我们可以写些代码测试上面的代码是否正确。通过下图我们可以发现程序的运行如我们所料缺省参数起了作用。 也许有细心的读者疑问t1的创建不可以写为如下的形式么符合我们以往对于无参函数的认知但构造函数是个特殊的函数创建类的对象是个特殊的过程。
Date t1(); 如果我们抛弃上面日期类的认识单纯的看这一段代码他会是什么意思。我们分析这几个符号首先是一个类型然后是一个标识符最后是即函数调用符。这不就是个函数声明语句么但从我们刚学的构造函数的角度看似乎又是创建变量并且调用无参构造函数。 这就产生了歧义在计算机中凡是有歧义的都是错误的程序计算机程序必须是明确的这与我们的自然语言不同程序作为一种形式语言有严厉的规则。于是摆在眼前的问题就是解决Date t1();歧义的问题如果我们是本贾尼博士我相信大多人会保留函数声明的做法改变无参构造的调用。原因无他成本小如果改变函数声明的做法又要改造函数又要增加记忆无参不带括号显得十分合理。
析构函数 有创造就一定有毁灭这个世界充满二异面。析构函数与构造函数是一对函数析构函数在类的销毁时调用。
为什么要有析构函数 假设我们现在有如下的函数调用。
void test()
{Date t;int c 0
} 当这个test函数调用完之后随着test函数栈帧的释放Datec位置的内存被释放似乎没有什么问题但有些情况就十分特殊。 假设我们此时给日期类增加一个指针用于存储特定日期的信息将代码稍微改写一下。
class Date
{
public:Date( int year1, int month1, int day1){_day day;_month month;_year year;t (int*)malloc(4 * sizeof(int));}void Print(){printf(%d-%d-%d\n, _year, _month, _day);}private:int _year;int _month;int _day;int* t;
}; 此时我们在运行test函数就会发现内存泄露了。当然解决方法十分简单我们只需要在test函数结束前加上free即可如下代码。
void test()
{Date t;int c 0free(t.t);
} 但这个比初始化更容易忘记于是我们的本贾尼博士为了解决这个烦恼引入了析构函数的概念在对象销毁的时候调用。
析构函数的定义 析构函数的定义和构造函数的定义十分的相似。他的语法如下。 1.函数名字必须为 ~类名相当于在构造函数名字前加了个~取反 2.函数没有返回类型没有形参其实有个隐藏的this指针可以看上篇文章 3.在对象销毁时自动的调用。 4.不能重载 析构函数没有形参自然也就无法重载了。我们使用析构函数就可以完成堆区内存的释放了。 同构造函数一样如果我们没有自己定义析构函数编译器会帮我们自动的生成默认析构函数此时对于内置类型不做处理对于自定义类型调用他的析构函数。此时我们要明白指针也是内置类型他不会帮我们释放指针所指的空间。
析构函数的使用 析构函数的使用也十分简单例如上述改装的日期类函数便可以写如下的析构函数。
~Date()
{free(t);
} 这样我们就可以在对象销毁的时候自动释放堆区的空间而不需要我们手动的释放了。 由此可见本贾尼博士对于C的优化十分戳中痛点让编译器帮我们处理许多的问题简化我们自己的操作。
拷贝构造函数
引入 听名字就可以判断出他是一种特殊的构造函数重点在于拷贝。对于单个数据我们可能手动的赋值但对于多个数据就有了拷贝复制操作。同样在初始化的操作中我们可能想要创造两个完全相同的日期类对象便可以采用下述代码。 两次采用相同的初始化但我们可以采用接下来要讲的拷贝构造函数简化代码。
int main()
{Date t1(2024, 4, 18);Date t2(2024, 4, 18);t1.Print();t2.Print();return 0;
}
拷贝构造函数定义 拷贝构造函数他属于特殊的构造函数当然要满足构造函数的要求名字为类名没有返回值在创建对象的时候自动调用他还有个特殊规定形参必须是类的引用这就可以与普通的构造函数进行重载将上面日期类重载拷贝构造函数如下。
Date(Date d)
{_day d._day;_month d._month;_year d._year;t (int*)malloc(4 * sizeof(int));memcpy(t, d.t, 4 * sizeof(int));
} 拷贝构造函数的使用 于是上面的代码我们就可以简写为如下代码调用拷贝构造函数创建两个相同的日期类。
int main()
{Date t1(2024, 4, 18);Date t2(t1);t1.Print();t2.Print();return 0;
}相信大家看到了上面在处理指针指向的数组时单独开辟了一段空间我们不可以直接写td.t么答案是显而易见的不可以。如下图。 我们俗称为浅拷贝只是将原来的数据按字节的拷贝而不做任何的处理对于用指针开辟的数组就会造成多个对象指向同一块内存空间从而造成多次释放同一块内存。如下图 而我们的默认拷贝构造函数就是按字节拷贝如果对于指针指向数组这种情况不做任何处理就会报错。所以我们有时需要自己写拷贝构造函数进行特殊处理。 其次我们来谈一个问题既然是拷贝一个对象我们可以把形参Date改为Date么对于现在的编译器而言不允许存在形参只为为Date的构造函数他会报出如下错误。 为什么呢我们在调用函数的时候会先传递参数如果参数的类型为Date实际上我们会把当前对象的地址传递过去为什么引用传地址可以看这篇博客在这里就不过多赘述了从C到C过渡知识 下深入理解引用与指针的关系-CSDN博客我们传递地址直接对其变量取地址传递就可以了。 但如果形参为Date会怎么样我们会先开辟一份空间分配给Date实例化后的对象d然后调用d的拷贝构造函数d的拷贝构造函数又会开辟一份空间给实例化对象然后调用其实例化对象的拷贝构造函数陷入一个死循环故我们不可以将形参写为Date。 拷贝构造函数的使用还有一种方式就是在创建对象的时候用可能祖师爷觉得符合直观的感觉就加上了这条规定使用如下。 但一定记住只有在创建变量的使用用是拷贝构造函数在创建后用就是赋值重载函数也就是我们接下来要讲的函数。
赋值重载函数 引入 回顾我们引入拷贝构造函数的原因为了简单的让两个对象的相同其实比起在初始化的时候让两个对象相同我们更多的情况是在定义完变量后让他与其他对象相同赋值重载函数就是对操作符进行重载使两个类的对象可以像内置类型一样简单的复制。 这样便可以提高代码的可读性相较于函数而言使用更加符合人的习惯。
赋值重载函数的定义 赋值重载函数是对操作符进行重载所以要求和操作符重载的要求一样。但为了高效使用我们通常定义如下模式。 Date operator(const Date d){_day d._day;_month d._month;_year d._year;t (int*)malloc(4 * sizeof(int));memcpy(t, d.t, 4 * sizeof(int));return *this;} 参数类型写为const Date d如果写为Date d又要去调用Date的构造函数那么这样就会白白的浪费内存而传引用就可以大大提高效率。其次我们在调用运算符的时候不会修改等号右边的值尽管我们可以在函数的内部实现但不要将运算符偏离原来的意思写出防御性代码。因此我们就可以将参数写为const Date d形式及提高效率又预防不测。 最后我们为什么要将函数的返回值写为Date呢我们看如下代码。 我们可能出现连续赋值的情况这个时候就需要我们返回Date为什么不写Date原因如上。 最后我们再来区分以下赋值重载函数和拷贝构造函数。读者可看如下代码。
int main()
{Date t1(2024, 4, 18);Date t2t1;Date t3(t2);t3 t2 ;t1.Print();t2.Print();return 0;
} Date t2t1 Date t3(t2);这两个调用的使拷贝构造函数而t3 t2 ;调用的是赋值重载函数。 如果两边的对象都是创建好的对象分配过了空间那么调用的就是赋值重载函数如果左边是刚创建变量就是拷贝构造函数。 其实在vs中还有种技巧判断VS中图片如下。 我们可以发现调用拷贝构造函数的是黑色而调用赋值重载函数的被标为了特殊颜色这也可以作为判断的一点技巧。
取地址运算符重载 这个成员函数我们自定义的不多一般用编译器帮我们默认生成的就可以了.他的实现如下
Date* operator(){return this ;}
const成员函数 但有些时候我们对const成员取地址但const 成员不可修改形参就要写为const最终代码如下。
const Date* operator()const{return this ;}第一个const是用来修饰返回值的第二个const是修饰隐藏的this指针的上述的两个函数又可以构成重载。一般我们用编译器生成的就可以了。 当我们需要对隐藏的tihis指针修饰常量的时候在最后加上const就可以了。这看起来有些奇怪但也是无奈之举为了减小复杂度将参数this隐藏而又要修饰this只能在创造规则了。
总结 到此我们就认识完了类的六大成员函数如果文章又错误欢迎在评论区指正。由上述可得类的六大默认成员函数最重要的是前四个而前四个中构造函数又是十分重要的。下一篇文章我将与大家共同实现一个日期类帮我们巩固知识更好的理解类和对象。、 喜欢的点点关注和赞吧