做网站成功,开源wiki做网站,网站建设网站制作有限,天元建设集团有限公司济南第六建筑工程分公司#x1f497;个人主页#x1f497; ⭐个人专栏——C学习⭐ #x1f4ab;点击关注#x1f929;一起学习C语言#x1f4af;#x1f4ab; 目录 导读1. 默认成员函数2. 构造函数2.1 引入2.2 特性2.3 默认构造函数 3. 析构函数3.1 概念3.2 特性3.3 默认析构函数 4. 拷贝构造函… 个人主页 ⭐个人专栏——C学习⭐ 点击关注一起学习C语言 目录 导读1. 默认成员函数2. 构造函数2.1 引入2.2 特性2.3 默认构造函数 3. 析构函数3.1 概念3.2 特性3.3 默认析构函数 4. 拷贝构造函数4.1 概念4.2 特性4.3 默认拷贝构造函数 导读
我们上次讲了类和对象的一些定义相关的知识今天我们进一步的来讲构造函数、析构函数和拷贝构造函数。
1. 默认成员函数
如果一个类中什么成员都没有简称为空类。 但是 空类中并不是什么都没有。 类有六个默认的成员函数任何类在什么都不写时编译器会自动生成以下6个默认成员函数。 默认成员函数用户没有显式实现编译器会生成的成员函数称为默认成员函数。
2. 构造函数
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(2024, 1, 30);d1.Print();Date d2;d2.Init(2024, 1, 31);d2.Print();return 0;
}对于Date类我们可以使用成员函数Init()给对象设置日期但如果每次创建对象时都调用该方法设置信息未免有点麻烦。
那有没有什么办法能在创建对象时自动将我们要传递的内容放置进去呢 那就需要我们的构造函数了。 构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用以保证每个数据成员都有 一个合适的初始值并且在对象整个生命周期内只调用一次。 2.2 特性
构造函数是特殊的成员函数需要注意的是构造函数虽然名称叫构造但是构造函数的主要任务并不是开空间创建对象而是初始化对象。
构造函数的特点包括
函数名与类名相同。无返回值。对象实例化时编译器自动调用对应的构造函数。构造函数可以重载。
class Date
{
public:Date() //无参构造函数{_year 1;_month 1;_day 1;}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;//调用无参构造函数d1.Print();Date d2(2024, 1, 31);//调用带参构造函数d2.Print();return 0;
}不给参数时就会调用 无参构造函数给参数则会调用 带参构造函数。 注意事项
如果通过无参构造函数创建对象时对象后面不用跟括号否则就成了函数声明。
class Date
{
public:Date() //无参构造函数{_year 1;_month 1;_day 1;}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(); //这样不能调用无参初始化return 0;
}这里如果调用带参构造函数我们需要传递三个参数 如果类中没有显式定义构造函数则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;
};int main()
{// 将Date类中构造函数屏蔽后代码可以通过编译因为编译器生成了一个无参的默认构造函Date d1;d1.Print();return 0;
}为什么是随机值呢我们接着引入下一特性。
2.3 默认构造函数
我们上述使用默认构造函数时生成的默认值似乎并没有什么用处。 C把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型如int/char…自定义类型就是我们使用class/struct/union等自己定义的类型看看下面的程序就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。 class Time
{
public:Time(){cout Time() endl;_hour 0;_minute 0;_second 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}注意 C11 中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在类中声明时可以给默认值。
class Time
{
public:Time(){cout Time() endl;_hour 0;_minute 0;_second 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
public:void Print(){cout _year - _month - _day endl;}
private:// 基本类型(内置类型)int _year 1970;int _month 1;int _day 1;// 自定义类型Time _t;
};
int main()
{Date d;d.Print();return 0;
}无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。
**注意**无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为是默认构造函数。
3. 析构函数
3.1 概念
析构函数是一种特殊的成员函数用于在对象销毁时执行清理工作。它的名称与类名相同前面加上一个波浪线~。在C中每个类都可以有一个析构函数它会在对象的生命周期结束时被自动调用。
3.2 特性
析构函数是特殊的成员函数其特征如下
析构函数名是在类名前加上字符 ~。无参数无返回值类型。一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。注意析构函数不能重载对象生命周期结束时C编译系统系统自动调用析构函数。
class Date {
public:Date(int year 1, int month 0, int day 0) {_year year;_month month;_day day;}void Print() {printf(%d-%d-%d\n, _year, _month, _day);}~Date() {// Date 类没有资源需要清理所以Date不实现析构函都是可以的cout ~Date() endl; // 测试一下让他吱一声}private:int _year;int _month;int _day;
};int main(void)
{Date d1;d1.Print();Date d2(2024, 1, 31);d2.Print();return 0;
}我们在之前学习数据结构时多用malloc等函数来开辟空间最后也要写个函数释放空间但有时候我们会忘记释放这时就需要我们的析构函数了我们不用特意的去调用系统会自己帮我们调用。
typedef int StackDataType;
class Stack {
public:/* 构造函数 - StackInit */Stack(int capacity 4) { // 这里只需要一个capacity就够了默认给4利用缺省参数_array (StackDataType*)malloc(sizeof(StackDataType) * capacity);if (_array NULL) {cout Malloc Failed! endl;exit(-1);}_top 0;_capacity capacity;}/* 析构函数 - StackDestroy */~Stack() { free(_array);_array nullptr;_top _capacity 0;}private:int* _array;size_t _top;size_t _capacity;
};int main(void)
{Stack s1;Stack s2(2); return 0;
}3.3 默认析构函数
关于编译器自动生成的析构函数是否会完成一些事情呢下面的程序我们会看到编译器生成的默认析构函数对自定类型成员调用它的析构函数。 和默认构造函数一样
对于 “内置类型” 的成员变量不作处理对于 “自定义类型” 的成员变量会调用它对应的析构函数。
class Time
{
public:~Time(){cout 调用了time的析构函数 endl;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year 1;int _month 1;int _day 1;// 自定义类型Time _time;
};int main()
{Date d1;return 0;
} 如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数比如 Date类有资源申请时一定要写否则会造成资源泄漏比如Stack类。
4. 拷贝构造函数
4.1 概念
拷贝构造函数是一种特殊的构造函数用于创建对象的拷贝。它接受与对象类型相同的另一个对象作为参数并使用该参数的值来初始化新对象。拷贝构造函数通常用于实现深拷贝即创建一个对象的独立副本而不是仅复制指针或引用。 拷贝构造函数的语法如下
类名(const 类名 另一个对象)
{// 初始化新对象的成员变量
}
4.2 特性
拷贝构造函数也是特殊的成员函数其特征如下
拷贝构造函数是构造函数的一个重载形式。拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用。
class Date
{
public:Date(int year 2024, int month 1, int day 31){_year year;_month month;_day day;}void Print() {printf(%d-%d-%d\n, _year, _month, _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;Date d2(d1);d1.Print();d2.Print();return 0;
}为什么要用引用呢 如果拷贝构造函数使用了对象作为参数而不是引用那么在调用拷贝构造函数时又需要创建一个新的对象这会引起一次不必要的拷贝构造操作。而如果使用引用作为参数就可以直接引用原对象避免了对象的拷贝。
此外如果拷贝构造函数使用了对象作为参数在进行传递时会调用拷贝构造函数而拷贝构造函数又需要调用拷贝构造函数这会导致无限递归的问题。而使用引用作为参数可以避免这种无限递归的情况。
4.3 默认拷贝构造函数
若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。
class Date {
public:Date(int year 0, int month 1, int day 1) {_year year;_month month;_day day;}// Date(Date d) {// _year d._year;// _month d._month;// _day d._day;// }void Print() {printf(%d-%d-%d\n, _year, _month, _day);}private:int _year;int _month;int _day;
};int main(void)
{Date d1(2024, 1, 31);Date d2(d1);d1.Print();d2.Print();return 0;
} 默认拷贝构造函数似乎和前面的默认构造函数以及默认析构函数不太一样它能够解决我们的需求。 但是 这并不意味着我们不用写拷贝构造函数 类中如果没有涉及资源申请时拷贝构造函数是否写都可以一旦涉及到资源申请时则拷贝构造函数是一定要写的否则就是浅拷贝。
拷贝构造函数典型调用场景
使用已存在对象创建新对象函数参数类型为类类型对象函数返回值类型为类类型对象
class Date
{
public:Date(int year, int minute, int day){cout Date(int,int,int): this endl;}Date(const Date d){cout Date(const Date d): this endl;}~Date(){cout ~Date(): this endl;}
private:int _year;int _month;int _day;
};
Date Test(Date d)
{Date temp(d);return temp;
}
int main()
{Date d1(2024, 1, 31);Test(d1);return 0;
}