网站建设一屏式网站,tp框架可以做网站吗,天津网站制作专业,自己的网站发文章怎么做外链目录 1.类的6个默认成员函数
2. 构造函数
2.1 构造函数概念的引出
2.2 构造函数的特性
3. 析构函数
3.1 析构函数的概念
3.2 特性
未使用构造与析构的版本
使用了构造与析构函数的版本
4. 拷贝构造函数
4.1 拷贝构造函数的概念
4.2 特性
结语 本节我们来认识…目录 1.类的6个默认成员函数
2. 构造函数
2.1 构造函数概念的引出
2.2 构造函数的特性
3. 析构函数
3.1 析构函数的概念
3.2 特性
未使用构造与析构的版本
使用了构造与析构函数的版本
4. 拷贝构造函数
4.1 拷贝构造函数的概念
4.2 特性
结语 本节我们来认识一些类的默认成员函数。
1.类的6个默认成员函数
如果一个类中什么都不写我们简称它为空类。
但空类中真的什么都没有吗 不是这样的任何类在我们什么都不写的情况下编译器会自动生成六个默认成员函数。 默认成员函数当用户没有显式定义时编译器自动生成的成员函数。 2. 构造函数
构造函数是六个默认成员函数中最为重要的成员函数。
2.1 构造函数概念的引出
为什么要有构造函数呢
我们来看看这段示例代码
class Date
{
public:void Init(int year, int month, int day){_year year;_month month;_day day;}void showinfo(){cout _year / _month / _day endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Init(2024, 2, 1);d1.showinfo();Date d2;d2.Init(1997, 1, 1);d2.showinfo();return 0;
} 对于Date类创建对象时我们可以调用公有方法Init() 来初始化对象但我们每次创建对象时都需要调用它会显得有一些麻烦那么有没有一种方法在我们创建对象的同时就能完成对他的初始化呢 答案是有的。构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用以保证每个数据成员都有 一个合适的初始值并且在对象整个生命周期内只调用一次。 2.2 构造函数的特性
构造函数是特殊的成员函数构造函数虽然名称叫构造但是构造函数的主要任务并不是开空间创建对象而是初始化对象。 其特征如下 1. 函数名与类名相同。 2. 无返回值。 3. 对象实例化时编译器自动调用对应的构造函数。 4. 构造函数可以重载。 我们将上面的Date类代码进行改造 class Date
{
public:Date()//无参构造函数{}Date(int year, int month, int day)//带参构造函数{_year year;_month month;_day day;}void showinfo(){cout _year / _month / _day endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;//调用无参构造函数d1.showinfo();Date d2(1997, 1, 1);//调用带参构造函数d2.showinfo();Date d3();return 0;
} 在这里我们看到d3的用法是调用无参函数调用无参函数是不能在对象后加括号的。 当我们调用无参构造函数时发现输出的成员变量都是随机值我们接着往下看。 5. 如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦 用户显式定义编译器将不再生成。 我们将显式定义的构造函数全部注释然后再调用无参默认构造结果如下 6. 关于编译器生成的默认成员函数很多朋友会有疑惑不实现构造函数的情况下编译器会 生成默认的构造函数。但是看起来默认构造函数又没什么用d对象调用了编译器生成的默 认构造函数但是d对象_year/_month/_day依旧是随机值。也就说在这里编译器生成的 默认构造函数并没有什么用 解答C把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型如int/char...自定义类型就是我们使用class/struct/union等自己定义的类型看看 下面的程序就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员 函数。 class Time
{
public:Time(){_hour 1;_minute 30;_second 47;}
private:int _hour;int _minute;int _second;
};
class Date
{
public:void showinfo(){cout _year / _month / _day endl;}private:int _year;int _month;int _day;Time _t;
};int main()
{Date d1;d1.showinfo();return 0;
} 注意C11 中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在 类中声明时可以给默认值 使用如下 class Time
{
public:Time(){_minute 30;_second 47;}
private:int _hour1;int _minute;int _second;
};
class Date
{
public:void showinfo(){cout _year / _month / _day endl;}private:int _year1997;int _month10;int _day9;Time _t;
};int main()
{Date d1;d1.showinfo();return 0;
} 7. 无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。注意无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为是默认构造函数所以默认构造函数有三种。不需要传参就可以调用的都可以称为默认构造函数。 下面这段代码就无法正常执行。 class Date
{
public:Date(){}Date(int year2020, int month1, int day1){this-_year year;this-_month month;this-_day day;}void showinfo(){cout _year / _month / _day endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;return 0;
} 因为全缺省构造函数与无参构造函数都属于默认构造函数调用不明确。默认构造函数只能有一个。 3. 析构函数
3.1 析构函数的概念
学习了上面的构造函数我们知道了对象是如何创建的那么对象是如何销毁的呢 简单来说对象通过调用析构函数清空对象的内容然后由编译器销毁空间。 析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数完成对象中资源的清理工作。 3.2 特性 析构函数是特殊的成员函数其特征如下 1. 析构函数名是在类名前加上字符 ~。 2. 无参数无返回值类型。 3. 一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。注意析构函数不能重载。 4. 对象生命周期结束时编译系统自动调用析构函数。 这里我们借用c实现Stack的部分代码做实例。如果想看完整代码的同学可以点击链接查看。
目录九c实现类封装Stack
未使用构造与析构的版本
typedef int DataType;
class Stack
{
public://初始化void STInit(){_a (DataType*)malloc(sizeof(DataType) * 4);if (_a NULL){perror(malloc fail);return;}_size 0;_capacity 4;}void STPush(DataType x){_a[_size] x;_size;}//销毁void STDestory(){if (_a NULL)return;free(_a);_a NULL;_size 0;_capacity 0;}private:DataType* _a;int _size;int _capacity;
};int main()
{Stack ST;//创建对象ST.STInit();//初始化对象ST.STPush(1);//压栈ST.STPush(2);ST.STDestory();//清空对象return 0;
}
使用了构造与析构函数的版本
typedef int DataType;
class Stack
{
public://初始化Stack(){_a (DataType*)malloc(sizeof(DataType) * 4);if (_a NULL){perror(malloc fail);return;}_size 0;_capacity 4;}void STPush(DataType x){_a[_size] x;_size;}//销毁~Stack(){if (_a NULL)return;free(_a);_a NULL;_size 0;_capacity 0;}private:DataType* _a;int _size;int _capacity;
};int main()
{Stack ST;//创建对象同时系统自动调用构造函数进行初始化ST.STPush(1);//压栈ST.STPush(2);return 0;//对象生命周期结束系统自动调用析构函数清空对象内容。
}
我们看这段代码
class Time
{
public:Time(){cout Time() endl;}~Time(){cout ~Time() endl;}
private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year 2020, int month 1, int day 1){cout Date() endl;}~Date(){cout ~Date() endl;}
private:int _year;int _month;int _day;Time _t;
};int main()
{Date d1;return 0;
}
在这段代码里有两个类Date类的成员变量中有Time类的对象对于两个类的构造与析构我们都只是做了打印函数名的操作。 我们看到只是创建了一个Date类的d1对象但却调用了两个类的构造与析构根据打印内容我们可以明晰函数的调用顺序。那么为什么会出现上面这个结果呢 内置类型成员的销毁不需要资源清理而销毁类中的自定义类型变量时需要调用其析构函数进行清理。 因为Time类的对象是Date类的成员变量因此必须先创建Time类的对象才能创建Date类对象。同时必须先调用Date类的析构函数然后调用Date类中自定义类型的析构函数。 也可以这样理解
//在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. 如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数比如 Date类有资源申请时一定要写否则会造成资源泄漏比如Stack类。 有兴趣的同学可以研究一下不同存储区对象的析构。 4. 拷贝构造函数
4.1 拷贝构造函数的概念
在现实生活中我们见过两个一模一样的人并称其为双胞胎。 那么在创建对象时能不能创建一个与已存在对象一模一样的新对象呢
在之前的学习中我们可以用拷贝来实现在c中同样可以。 拷贝构造函数只有单个形参该形参是对本类类型对象的引用(一般常用const修饰)在用已存在的类类型对象创建新对象时由编译器自动调用。 4.2 特性
拷贝构造函数也是特殊的成员函数其特征如下 1. 拷贝构造函数是构造函数的一个重载形式。 2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用。 下面这段代码是正确示范
class Date
{
public:Date(int year 2020, 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 showinfo(){cout _year / _month / _day endl;}
private:int _year1997;int _month10;int _day9;
};int main()
{Date d1(2022, 9, 13);Date d2(d1);d2.showinfo();return 0;
} 关于第二点为什么传值方式会无穷递归呢 这是因为形参就是原对象的拷贝需要调用拷贝函数但拷贝函数需要传入对象的形参因此构成了无限递归拷贝的现象。 3. 若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。 class Date
{
public:Date(int year 2020, int month 1, int day 1){_year year;_month month;_day day;}void showinfo(){cout _year / _month / _day endl;}
private:int _year1997;int _month10;int _day9;
};int main()
{Date d1(2022, 9, 13);Date d2(d1);d2.showinfo();return 0;
}
这段代码里我们并没有显式实现拷贝构造函数但结果依旧。 注意在编译器生成的默认拷贝构造函数中内置类型是按照字节方式直接拷贝的而自定 义类型是调用其拷贝构造函数完成拷贝的 既然编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了还需要自己显式实现吗 我们来看看Stack类是怎么做的
typedef int DataType;
class Stack
{
public://初始化Stack(){_a (DataType*)malloc(sizeof(DataType) * 4);if (_a NULL){perror(malloc fail);return;}_size 0;_capacity 4;}void STPush(DataType x){_a[_size] x;_size;}//销毁~Stack(){if (_a NULL)return;free(_a);_a NULL;_size 0;_capacity 0;}private:DataType* _a;int _size;int _capacity;
};int main()
{Stack ST;ST.STPush(1);ST.STPush(2);ST.STPush(3);Stack st(ST);return 0;
}
在这段代码里我们并没有显式实现Stack类的拷贝构造上面的Date类同样没有但他们的结果却截然不同。 很明显这样做是错的。那这是为什么呢 注意类中如果没有涉及资源申请时拷贝构造函数是否写都可以一旦涉及到资源申请 时则拷贝构造函数是一定要写的否则就是浅拷贝。 5. 拷贝构造函数典型调用场景 使用已存在对象创建新对象 函数参数类型为类类型对象 函数返回值类型为类类型对象 小建议为了提高程序效率一般对象传参时尽量使用引用类型返回时根据实际场景能用引用尽量使用引用。 下期再见