目录
空类中都有什么
默认成员函数
构造函数
简介
特性
注意
总结
析构函数
简介
特性
注意
总结
空类中都有什么
先看下面一段代码:
class Date
{
};
int main()
{
Date d1;
std::cout << sizeof(Date) << std::endl;
std::cout << sizeof(d1) << std::endl;
system("pause");
return 0;
}
代码运行结果为1,可是类中没有任何东西,为什么结果是1呢?
这是因为1字节是占位的,用来标记类,同时空类中并不是什么都没有,而是有编译器生成的6个默认函数。
默认函数就是,用户没有显示生成,编译器会默认生成的函数;
默认成员函数
函数名 | 作用 |
构建函数 | 对类进行初始化 |
析构函数 | 对类进行清理 |
拷贝构建函数 | 用同类对象进行类的初始化及创建 |
赋值重载函数 | 赋值重载主要是把一个对象赋值给另一个对象 |
const成员函数 | |
取地址函数 |
构造函数
简介
构造函数是一个特殊的成员函数,函数名与类名相同,没有返回值,创建类对象时由编译器自动调用,保证给每个类有一个合适的值,在整个函数的生命周期只调用一次;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
};
int main()
{
Date _d1;
system("pause");
return 0;
}
特性
1、构造函数的函数名与类名相同;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
};
上面的代码中,构造函数名与类名相同都是Date;
2、没有返回值;
构造函数没有返回值,注意void(空类型)是一种返回值类型,构造函数连void也没有;
3、创建对象时由编译器自动调用;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
};
int main()
{
Date _d1;
system("pause");
return 0;
}
_d1创建后结果为个变量为1;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
};
int main()
{
Date _d1(2023,10,12);
system("pause");
return 0;
}
_d1创建时传值,创建后各变量分别为2023,10,12
//无参创建对象的格式:
//传递参数创建时,格式为Date _d1(2023,10,11);
//为什么不传参时不用Date _d1();格式呢?
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
};
int main()
{
Date _d1();
system("pause");
return 0;
}
经过测试,该代码可以运行,但是对象不会创建;
这是因为 Date _d1()的语句并不是创建,而是函数声明其中Date是函数返回值,_d1是函数名;
4、构造函数可以重载;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year , int month , int day )
{
_year = year;
_month = month;
_day = day;
}
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
};
int main()
{
Date _d1;
system("pause");
return 0;
}
//代码运行的结果_d1中的变量都为1
注意:构造函数 可以重载,但是如果写的不当,调用时会产生二义性;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year=1 , int month=1 , int day=1 )
{
_year = year;
_month = month;
_day = day;
}
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
};
int main()
{
Date _d1;
system("pause");
return 0;
}
5、如果类中没有显式定义,那么编译器会生成一个无参的默认构造函数,如果显式定义,那么不会生成;
class Date
{
private:
int _year;
int _month;
int _day;
};
int main()
{
Date _d1;
system("pause");
return 0;
}
注意:编译器生成的构造函数,不会将内置类型的值初始化,各变量的值还是乱码;
显示生成构造函数,编译器后都不会再生成默认构造函数;
class Time
{
public:
Time()
{
std::cout << "Time()" << std::endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year=2023;
int _month=2;
int _day=2;
// 自定义类型
Time _t;
public:
Date(int year , int month , int day )
{
_year = year;
_month = month;
_day = day;
}
};
int main()
{
Date d;
system("pause");
return 0;
}
6、内置类型编译器生成的默认成员函数没有用,自定义类型会调用它的默认成员函数;
class stack
{
private:
int * _data;
int _size;
int _capacity;
public:
stack(int capacity=4)
{
int *newdata = (int *)malloc(sizeof(int)*capacity);
if (newdata == nullptr)
{
perror("malloc fail");
exit(-1);
}
_data = newdata;
_capacity = capacity;
_size = 0;
}
~stack()
{
free(_data);
_data = nullptr;
_capacity = 0;
_size = 0;
}
};
class queue
{
private:
stack _stpush;
stack _stpop;
};
int main()
{
queue _q1;
system("pause");
return 0;
}
上面的函数中, queue对象没有创建构建函数,但创建时还是进行了初始化,整个过程如下queue对象创建时,编译器生成了默认的成员函数,queue没有内置类型,所以其默认构造函数调用了stack的默认构造函数(不能调用构造函数,因为stack的构造函数没有参数)完成queue对象的创建,为了重复整个过程可以在queue中加入内置类型:
class Time
{
public:
Time()
{
std::cout << "Time()" << std::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;
system("pause");
return 0;
}
可以看到_t的内容调用了默认构造函数进行初始化,但是d的部分没有,但有一个有趣的情况:
class stack
{
public:
stack(int capacity=4)
{
_num = (int *)malloc(sizeof(int)*capacity);
if (_num == nullptr)
{
perror("malloc fail");
exit(-1);
}
_capacity = capacity;
_size = 0;
}
private:
int * _num;
int _size;
int _capacity;
};
class queue
{
private:
stack _s1;
stack _s2;
stack _s3;
int _l;
};
int main()
{
queue _q1;
system("pause");
return 0;
}
在加入了malloc后,_l的值会被初始化,这是因为什么呢?
7、无参构造函数,全缺省构造函数,编译器生成的默认构造函数,才是默认构造函数(默认构造函数不需要传参)
注意
C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值:
class Time
{
public:
Time()
{
std::cout << "Time()" << std::endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year=2023;
int _month=2;
int _day=2;
// 自定义类型
Time _t;
};
int main()
{
Date d;
system("pause");
return 0;
}
在Date类中给3个内置变量默认值,创建后d中的3个变量自然被赋值,但加上默认构建函数后,默认值没有用;
class Date
{
private:
// 基本类型(内置类型)
int _year=2023;
int _month=2;
int _day=2;
// 自定义类型
Time _t;
public:
Date(int year = 2022, int month = 10, int day = 19)
{
_year = year;
_month = month;
_day = day;
}
};
总结
构建函数就是对类进行初始化的函数,它有七个特点:
一、函数名就是类名;
二、没有返回值;
三、构建函数可以重载(可以有很多个),但是默认构建函数不行(有且只能有一个);
四、创建对象时由编译器自动调用(有参数时创建方式 Date d(2,1,3),无参时创建方式Date d;不可以写成Date d();这样会被编译器视为函数声明);
五、如果没有显式定义构造函数,编译器会自动生成默认构造函数,如果显示定义构造函数(无论是否默认构造函数)编译器不生成默认构造函数;
六、编译器生成的默认构造函数,内置类型不会初始化,自定义类型,编译器会调用其默认函数;
七、默认构造函数一定是无参的,如果有参就不是默认构造函数;
析构函数
简介
析构函数的作用与构造函数相反,是类销毁时,完成对象中资源清理工作,它不是销毁对象,销毁对象的工作由编译器完成,它类似于destory,清理对象占据的资源。
class Date
{
private:
int _year=1;
int _month = 2;
int _day = 3;
public:
};
void func()
{
Date _d;
}
int main()
{
func();
system("pause");
return 0;
}
特性
1、析构函数的函数名是~类名;
2、析构函数没有返回值没有参数;
class stack
{
private:
int * _data;
int _size;
int _capacity;
public:
stack(int capacity = 4)
{
_data = (int *)malloc(sizeof(int)*capacity);
if (_data == nullptr)
{
perror("malloc fail");
exit(-1);
}
_capacity = capacity;
_size = 0;
}
~stack()
{
free(_data);
_data = nullptr;
_capacity = 0;
_size = 0;
}
};
void func()
{
stack st;
}
int main()
{
func();
system("pause");
return 0;
}
3、析构函数只能有一个,不能重载;
析构函数只能有一个,由C++规则决定不可以重载,如果写两个析构函数编译时会报错。
4、析构函数没有显示定义时,系统会自动生成;
但是编译器生成析构函数对内置类型和自定义类型都不会进行清理,会造成数据泄漏;
//编译器生成析构函数的情况
class stack
{
//private:
public:
stack(int capacity=4)
{
_data = (int *)malloc(sizeof(int)*capacity);
if (_data == nullptr)
{
perror("malloc fail");
exit(-1);
}
_size = 0;
_capacity = 0;
}
int* _data;
int _size;
int _capacity;
};
void func()
{
stack s;
s._data[0] = 1;
s._data[1] = 2;
s._data[2] = 3;
s._data[3] = 4;
}
int main()
{
func();
int a=10;
system("pause");
return 0;
}
stack对象在func中创建,所以出了func该对象就会调用析构函数;
func调用完成后,对象中动态开辟空间中的数据没有被清理;
显示定义析构函数:
~stack()
{
if (_data != nullptr)
{
free(_data);
_data = nullptr;
_size = 0;
_capacity = 0;
}
}
经过显示定义析构函数,func调用完成后,动态开辟空间中的值被销毁;
5、析构函数对自定义成员类型会调用它的析构函数;
接下来先套个娃,创建一个queue类,其成员属性为stack,不创建析构函数:
class queue
{
public:
stack s;
};
void func()
{
queue q;
q.s._data[0] = 1;
q.s._data[1] = 2;
q.s._data[2] = 3;
q.s._data[3] = 4;
}
当func调用结束,对象q中动态开辟空间中的数据已经被销毁,虽然queue没有创建析构函数,但是编译器对自定义类型会调用它的析构函数,所以空间中的数据才会被销毁;
6、类中存在资源时,一定要写析构函数,不然会造成内存泄漏(内存泄漏是指向动态开辟空间中的数据没有被销毁);
注意
析构函数在使用时,自定义类型一定要写析构,不然会造成内存泄漏,对内置类型来说,其值由编译器控制,可以不写;
总结
什么情况下需要构建与析构呢,下面分别以日期类,栈类和队列类为例:
//日期类
class Date
{
private:
int _year;
int _month;
int _day;
};
//栈类
class stack
{
private:
int * _data;
int _size;
int _capacity;
};
//日期类
class queue
{
private:
stack _s1;
stack _s2;
};
日期类 | 栈类 | 队列类 | ||||
构建 | 需要 | 默认构建函数对内置类型没作用,需要显式实现 | 需要 | 栈类本质都是自定义类型,开辟动态空间需要构建函数定义 | 不需要 | 自定义类型调用栈类型的默认构建函数实现 |
析构 | 不需要 | 内置类型由编译器处理 | 需要 | 存在动态开辟空间需要析构函数销毁相关数据 | 不需要 | 调用栈类型的析构函数 |