建设公司网站需要准备哪些材料,网站排名优化如何做,像试淘网网站怎么建设,小说网站如何做书源【学习目标】 1. 类的6个默认成员函数 2. 构造函数 3. 析构函数 4. 拷贝构造函数
目录
一.类的6个默认成员函数
二. 构造函数
2.1 概念
2.2.特性
三.析构函数
3.1.概念
3.2 特性
四.拷贝构造函数
4.1.概念
4.2.特性 一.类的6个默认成员函数 如果一个类中什么成员… 【学习目标】 1. 类的6个默认成员函数 2. 构造函数 3. 析构函数 4. 拷贝构造函数
目录
一.类的6个默认成员函数
二. 构造函数
2.1 概念
2.2.特性
三.析构函数
3.1.概念
3.2 特性
四.拷贝构造函数
4.1.概念
4.2.特性 一.类的6个默认成员函数 如果一个类中什么成员都没有简称为空类。 空类中真的什么都没有吗 并不是任何类在什么都不写时编译器会自动生成以下6个默认成函数。 默认成员函数用户没有显式实现编译器会生成的成员函数称为默认成员函数。 class Date {}; 二. 构造函数
2.1 概念
class Date
{
public:void init(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;d1.init(2023, 11, 05);d1.print();return 0;
} 对于 Date 类可以通过 Init 公有方法给对象设置日期但如果每次创建对象时都调用该方法设置信息未免 有点麻烦那能否在对象创建时就将信息设置进去呢 构造函数是一个特殊的成员函数 1.名字与类名相同, 2.创建类类型对象时由编译器自动调用以保证每个数据成 员都有 一个合适的初始值 3.在对象整个生命周期内只调用一次。 2.2.特性 构造函数 是特殊的成员函数需要注意的是构造函数虽然名称叫构造 构造函数的主要任务 并不是开 空间创建对象而是 初始化对象。 特征如下 1. 函数名与类名相同。 2. 无返回值。 3. 对象实例化时编译器自动调用对应的构造函数。 4. 构造函数可以重载。 5. 如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成。 构造函数可以分成2类——无参构造函数和有参构造函数 class Date
{
public:/*void init(int year, int month, int day){_year year;_month month;_day day;}*/// 1.无参构造函数Date(){}// 2.有参构造函数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; // 调用无参构造函数Date d2(2015, 1, 1); // 调用带参的构造函数d1.print();d2.print();return 0;
} 注意如果通过无参构造函数创建对象时对象后面不用跟括号否则 就成了函数声明 以下代码的函数声明了d3函数该函数无参返回一个日期类型的对象 Date d3()不是很惯用的函数声明我换种形式 int func(),这样看是不是更清楚了这就是相当于无参函数的声明。int func(void). 以上的四点特性我已经一一列举展示出来接下来第5点。 特性5 5. 如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成。 类中显式定义构造函数就是显示上面的代码段编译器就不用给出帮助因为自己传参了。 类中没有显示定义构造函数C编译器会自动生成一个无参的默认构造函数。然后给出的成员变量的值都是随机值。 我们看一下这下面的代码段为什么显示“没有合适的默认构造函数可用” 错误原因在Date类中定义了其他构造函数那么编译器不会为你创建默认构造函数然而在编译器调用Date的默认构造函数时就会找不到d1是无参的对象而上面的默认构造函数是存在参数的所以编译器显示没有合适的默认构造函数可用。 解决方法加入默认构造函数与之匹配。 如果Date创建的对象d1传参和上面的构造函数参数个数类型匹配就说明找到合适的默认构造函数可用否则都得创建一个与之对应参数个数类型的默认构造函数即可。 特性6: 6. 关于编译器生成的默认成员函数很多童鞋会有疑惑不实现构造函数的情况下编译器会生成默认的 构造函数。但是看起来默认构造函数又没什么用d对象调用了编译器生成的默认构造函数但是d对象 _year/_month/_day依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用 解答 C把类型分成内置类型(基本类型)和自定义类型。 内置类型: 就是语言提供的数据类型如 int/char...自定义类型: 就是我们使用class/struct/union等自己定义的类型 看看下面的程序就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数构造函数 大家看这里的Date类与上面那个有什么区别是不是它的成员变量里既有内置类型又有自定义类型啊。但是我们现在并没有给Date类写构造函数那我们在main函数里直接拿Date去创建一个对象它自然就会去调用编译器自动生成的构造函数那内置类型不做处理我们不是还有一个自定义类型Time _t;呢我们说对于自定义类型编译器会自动去调用它对应的默认构造函数。 那我们在Time 类的默认构造函数里面故意加了一个打印 这里很明显编译器对于自定义类型成员_t去调用构造函数。 注意 C11 中针对内置类型成员不初始化的缺陷又打了补丁即 内置类型成员变量在类中声明时 可以给默认值. 这样如果我们不写构造函数内置类型的初始化就会按给定的缺省值进行初始化。 这里我需要强调一下自定义类型和内置类型的区别系统会默认给内置类型初始化一个随机值但是随机值是没有意义的虽然后来打了补丁可以给缺省值但是我们未来还会用到更多的类型例如自定义类型这时我们就需要自定义类型因此自定义所构造的构造函数可以更好的帮助我们进行初始化。 特性7 7. 无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。 注意无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为是默认构造函数。 我们上面的特性说了只能有一个默认构造函数这里出现了俩个默认构造函数所以就导致了对重载函数的调用不明确因为不知道到底调哪个默认构造函数。 这里我们要特别区分俩个名词默认成员函数和默认构造函数 默认成员函数任何类在什么都不写时编译器会自动生成以下6个默认成函数。默认构造函数类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数。 三.析构函数 我们在之前数据结构的学习中在学到栈的时候有一个与栈相关的非常经典的题目——括号匹配问题。 来看一下我们C语言写出来的代码我们进行判断之后需要return的地方可能有好几处但是呢每次return之前其实最好都要去调用一下StackDestroy把我们动态开辟的空间给销毁一下但是我们可能很容易会忘掉导致内存泄漏。 那现在我们学了C有没有什么好的办法可以帮助我们解决这个问题呢 可不可以像上面的构造函数自动初始化一样自动对对象中的资源进行清理呢 那当然是有的就是我们接下来要学习的析构函数。先了解概率吧。 3.1.概念 通过前面构造函数的学习我们知道一个对象是怎么来的那一个对象又是怎么没呢的 —— 析构函数 与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数完成对象中资源的清理工作。 3.2 特性 析构函数 是特殊的成员函数其 特征如下 1. 析构函数名是在类名前加上字符 ~。(~就相当于运算符里面的按位取反操作符) 2. 无参数无返回值类型。 3. 一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。 注意析构函数不能重载 4. 对象生命周期结束时C编译系统系统自动调用析构函数。 typedef int DataType;
class Stack
{
public:Stack(size_t capacity 3){_array (DataType*)malloc(sizeof(DataType) * capacity);if (NULL _array){perror(malloc申请空间失败!!!);return;}_capacity capacity;_size 0;}void Push(DataType data){// CheckCapacity();_array[_size] data;_size;}// 其他方法...~Stack(){if (_array){free(_array);_array nullptr;_capacity 0;_size 0;}}
private:DataType* _array;int _capacity;int _size;
};
int main()
{Stack s;s.Push(1);s.Push(2);
return 0;
} 特性5 5. 关于编译器自动生成的析构函数是否会完成一些事情呢 下面的程序我们会看到编译器生成的默认析构函数对自定类型成员调用它的析构函数。 class Time
{
public:~Time(){cout ~Time() endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year 1970;int _month 1;int _day 1;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
} 这里我们没有给Date显式定义析构函数那d声明周期结束时就会调用编译器自己生成的默认析构函数那里面的内置类型不做处理当然也不用处理关键在于自定义类Time _t;申请的资源需要清理那我们看编译器自己生成的默认析构函数会不会调用Time _t类的析构函数 这是上面代码的运行结果。显然是调用了。 这时候我们就应该想一个问题了 ——在main方法中根本没有直接创建Time类的对象为什么最后会调用Time类的析构函数 因为main方法中创建了Date对象d而d中包含4个成员变量其中_year, _month, _day三个是内置类型成员销毁时不需要资源清理最后系统直接将其内存回收即可 而_t是Time类对象所以在 d销毁时要将其内部包含的Time类的_t对象销毁所以要调用Time类的析构函数。但是main函数中不能直接调用Time类的析构函数实际要释放的是Date类对象所以编译器会调用Date类的析构函数而Date没有显式提供则编译器会给Date类生成一个默认的析构函数目的是在其内部调用Time 类的析构函数即当Date对象销毁时要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数而是显式调用编译器为Date类生成的默认析构函数。 注意创建哪个类的对象则调用该类的析构函数销毁那个类的对象则调用该类的析构函数。 特性6 6. 如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数比如Date类有资源申请时一定要写否则会造成资源泄漏比如Stack类 。 四.拷贝构造函数 4.1.概念 在现实生活中可能存在一个与你一样的自己我们称其为双胞胎。 那在创建对象时可否创建一个与已存在对象一某一样的新对象呢 拷贝构造函数 只有单个形参 该形参是对本 类类型对象的引用 ( 一般常用 const 修饰 ) 在用 已存在的类类型 对象创建新对象时由编译器自动调用 。 4.2.特性 拷贝构造函数也是特殊的成员函数 其特征如下 1. 拷贝构造函数是构造函数的一个重载形式。 因为我们刚才上面说了嘛它的作用其实也是用来初始化对象的只不过参数类型指定了是我们当前类的类型嘛。所以它算是构造函数的一种重载形式。 2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用。 我们按照上面形式传参会导致显示一个错误编译没有运行就错了。 那相信大家刚才也注意到上面的概念了在拷贝构造函数的概念中其实就指明了说它的参数类型应该是类对象的引用。 确实我们这样修改之后就可以了。 2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用。 结合下面这张图给大家解释一下 ps图中还在形参前加了const大家可以先不管后面会解释。 大家想一下首先我们这里是用已有的类对象去创建一个相同的新对象类对象的拷贝所以会调用拷贝构造函数那要调用函数是不是要先传参啊而传值调用传的是啥形参是实参的一份临时拷贝是不是传的实参的拷贝那要拷贝实参是不是又是一个类对象的拷贝啊那既然是类对象的拷贝就又要调用拷贝构造函数那就又需要传参一传参就会再次调用拷贝构造函数那这样是不是就陷入一个死递归了。 直接用“”也可以这样也是拷贝构造。 上面一开始拷贝构造函数的概念中说它的形参一般用const修饰 为什么要加个const呢 其实很容易理解大家想形参d是用来干嘛的 是用来初始化我们新创建的对象的那我们肯定不希望形参d被修改所以加个const修饰 这样我们如果不小心写反了啥的是不是就直接报错了。 另外加const还有什么好处呢 大家想如果我们不加const但传过来的参数是const修饰的这样的话是不是根本就接收不了啊这个问题我们之前也讲了是不是属于权限放大了是不行的。但是如果我们加了const传过来的不管是否加const是不是都可以接收啊。 特性3 3. 若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成 拷贝这种拷贝叫做浅拷贝或者值拷贝。 那我们上面说了拷贝构造函数是构造函数的一种重载形式那其实就也属于是构造函数了那构造函数我们不写的话编译器不是会自动生成嘛那拷贝构造函数是不是也具有这样的特性呢 是的对于拷贝构造函数来说若未显式定义编译器也会生成默认的拷贝构造函数。 我们刚才不是对Date类实现了一个拷贝构造函数嘛先我们现在把它屏蔽调 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;d1.Print();Date d2(d1);d2.Print();Date d3 d1;d3.Print();return 0;
} 注意在编译器生成的默认拷贝构造函数中内置类型是按照字节方式直接拷贝的而自定义类型是调用其拷贝构造函数完成拷贝的。 那既然编译器自动生成的拷贝构造函数就可以帮助我们完成类对象的拷贝了那我们还需要自己写吗 特性4 那为了解决这个问题我们再来看这样一个类 4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了还需要自己显式实现吗当然像日期 类这样的类是没必要的。那么下面的类呢验证一下试试 typedef int DataType;
class Stack
{
public:Stack(int capacity 4){_array (DataType*)malloc(sizeof(DataType) * capacity);if (NULL _array){perror(malloc申请空间失败!!!);return;}_capacity capacity;_size 0;}void Push(DataType data){// CheckCapacity();_array[_size] data;_size;}// 其他方法...~Stack(){cout ~Stack endl;free(_array);_array NULL;_capacity 0;_size 0;}
private:DataType* _array;int _capacity;int _size;
};还是我们之前用过的这个栈Stack类大家看它的成员变量是不是也都是内置类型啊前面提到过指针也属于内置类型嘛。 那对于Stack这个类我们也是没写拷贝构造函数的那编译器自动生成的能不能完成下面这样的拷贝呢 int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);Stack s2(s1);return 0;
}为什么会这样呢刚才Date类不也都是内置类型为啥就没事呢 大家有没有注意到我们上面的特性3后面的一句话是默认的拷贝构造函数 拷贝对象 按内存存储字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。在这里其实就是对逐个成员变量依次进行拷贝里面存的是啥就把啥拷过去。 那原因其实就出现在这里我们来对比一下Date和Stack这两个类进行的拷贝 首先对于Date类来说进行这样的浅拷贝有没有问题啊。 是不是没问题啊一共12个字节的内容直接拷贝过去就行了嘛 但是对于Stack类来说呢 我们还是这样进行浅拷贝的话 我们有没有想过_array指向的空间在堆区malloc(是在堆区)在销毁对象的时候要销毁对象内部的成员变量在成员变量中有一个变量是指向该堆区内存的指针因此每销毁一个对象就要销毁对应的内存地址而浅拷贝会导致被销毁的两个对象中的那两个成员指针变量指向同一块内存从而导致该内存被销毁两次。 比如一个文件f1里面包含了各种资料我们需要再继续创建一个不同名文件f2但是文件里面的内容相同然后删除这个原本文件f1之前我们是不是需要拷贝这个文件然后才能删除。我们想想内容是在不同的文件里面的虽然是相同的我们销毁f1文件里面的内容是不是销毁f1文件销毁f2文件里面的内容是不是销毁f1和f2文件内容是占据不同的文件。 这就是深拷贝就是将内容构造拷贝时_array需要开一快同样的大小空间。 这样对两个对象进行操作就不会互相影响了。 Stack(const Stack st){_array (DataType*)malloc(sizeof(DataType) * st._capacity);if (NULL _array){perror(malloc申请空间失败!!!);exit(-1);}memcpy(_array, st._array, sizeof(DataType) * st._size);_capacity st._capacity;_size st._size;}没有问题了。 注意 类中如果没有涉及资源申请时拷贝构造函数是否写都可以一旦涉及到资源申请时则拷贝构 造函数是一定要写的否则就是浅拷贝。 总结在编译器生成的默认拷贝构造函数中内置类型是按照浅拷贝值拷贝进行拷贝的而自定义类型是调用其对应的拷贝构造函数完成拷贝的。 特性5 5. 拷贝构造函数典型调用场景 使用已存在对象创建新对象 函数参数类型为类类型对象 函数返回值类型为类类型对象 为了提高程序效率一般对象传参时尽量使用引用类型(减少拷贝)返回时根据实际场景能用引用尽量使用引用。 考研还是就业