目录
⑥c++将类型分为内置类型和自定义类型,内置类型就是语言提供的数据类型,例如上文中Data类中的year month day,而自定义类型就是在Data类中所包含的那个Time类类型。
⑦无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
③一个类中只能存在一个析构函数。若析构函数没有显示定义,系统会自动生成一个默认的析构函数。(析构函数无法重载,因为没有参数)
⑤如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。⑥栈的代码实现:
②参数只有一个,即为类类型对象的引用,使用传值方式编译器会直接报错,因为会造成无穷递归调用。
我们发现值传递会发生形参实例化,类类型实例化,会在进行调用拷贝构造函数,就会一直调用,因此结果为无穷递归。
③若没有显示定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象内存存储按字节完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝
一、构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次
构造函数虽然其名为构造,实际上的功能却是对类对象进行初始化。
使用:
①函数名与类名相同
②没有返回值
③对象实例化时自动调用对应的构造函数
④可以进行重载
class Data
{
public:
Data()
{}
Data(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data t1; // 调用无参构造函数
Data t2(2022,11,15);// 调用有参构造函数
Data t3(); // 这里的t3()体现出来了函数的声明,
// 调用无参构造函数时不能加上括号.
}
这里要进行区别t1与t3之间的异同点。
⑤没有对构造函数显示定义时,则c++编译器会自动生成一个默认的构造函数,一旦用户显示定义编译器将不再自己生成这个默认构造函数。这时候就容易编译报错,那么推荐我们在定义构造函数的时候将默认构造函数也书写出来。
默认构造函数的重要性: http://t.csdn.cn/KCccD
class Data
{
public:
// 没有写构造Data函数
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
//Data(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data t1;
// 语法上规定如果用户没有定义构造函数,则编译器就会自己进行构造一个构造函数
// 但和Data() {} 效果一样。
// vs 中没有对上一步骤进行构造函数调用,VS编译器认为执行没有意义,选择不进行执行来进行优化
t1.Print();
return 0;
}
可以看到上方对构造函数进行显示定义后,再调用默认构造函数就会出错。
什么时候会进行默认构造函数的生成,什么时候不会呢?
ps:当A类中包含B类,当A中没有显示定义默认构造函数时,我们知道编译器会进行优化即不进行A的默认构造函数的生成,可是如果当所包含的B中有显示定义的初始化B类对象的构造函数时,这时编译器就会对A进行默认构造函数的生成。
如下我对Data类中包含了一个自定义类型Time类。如果仅有Data类,VS编译器将不会展开默认构造函数,然而我们在data类中又调用了Time对象的默认构造函数,那么就会对Data进行默认构造函数的生成。
从汇编代码可以看到我们在Data类不包含Time类时直接调用Data默认构造函数时,没有汇编代码生成,然而在上面这种情况下,产生了汇编代码,从代码中可以看到不仅调用了Time类的默认构造函数,也调用了Data的默认构造函数。
class Time
{
public:
Time()
{
cout << "Time() " << endl;
_hour = 12;
_minute = 12;
_second = 12;
}
private:
int _hour;
int _minute;
int _second;
};
class Data
{
public:
// 没有写构造Data函数
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
// 内置类型
int _year;
int _month;
int _day;
// 自定义类型
Time t1;
};
int main()
{
Data t1;
return 0;
}
⑥c++将类型分为内置类型和自定义类型,内置类型就是语言提供的数据类型,例如上文中Data类中的year month day,而自定义类型就是在Data类中所包含的那个Time类类型。
c++11中补充: 在声明内置类型时我们可以对其进行赋初值
class Time
{
public:
Time()
{
cout << "Time() " << endl;
}
private:
int _hour = 11;
int _minute = 12;
int _second = 15;
};
class Data
{
public:
// 没有写构造Data函数
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
// 内置类型
int _year = 2022;
int _month = 11;
int _day = 16;
// 自定义类型
Time t1;
};
int main()
{
Data t1;
return 0;
}
结果:
⑦无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参的构造函数、全缺省的构造函数、还有我们没有写编译器自动生成的构造函数
都是默认构造函数
class Data
{
public:
//全缺省的构造函数
Data(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 无参的构造函数
Data()
{
_year = 1900;
_month = 1;
_day = 1;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d;
return 0;
}
报错显示如下:
二、析构函数
与构造函数的功能相反,局部对象的销毁是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对对象中资源的清理工作。
①析构函数的格式为类名前加上 ~ ;
②无参数无返回值
③一个类中只能存在一个析构函数。若析构函数没有显示定义,系统会自动生成一个默认的析构函数。(析构函数无法重载,因为没有参数)
④对象声明周期结束时,系统会自动调用析构函数。
⑤如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
⑥栈的代码实现:
#include<iostream>
using namespace std;
#include<assert.h>
#include<stdlib.h>
class Stack
{
public:
//void Init()
Stack() //默认的构造函数
{
_array = (int*)malloc(sizeof(int) * 10);
if (NULL == _array)
{
assert(false);
printf("fail \n");
return;
}
_size = 0;
_capacity = 10;
}
void Push(int data)
{
_array[_size] = data;
_size++;
}
void Pop()
{
if (Empty())
return;
else
_size--;
}
int top()
{
if (Empty())
return -1;
else
return _array[_size - 1];
}
bool Empty()
{
return 0 == _size;
}
//void Destory()
~Stack() // 析构函数
{
if (_array)
{
free(_array);
_array = NULL;
_size = 0;
_capacity = 0;
}
}
private:
int* _array;
int _size;
int _capacity;
};
void Test()
{
Stack s;
//s.Init();
s.Push(1);
s.Push(2);
cout << s.top() << endl;
s.Push(3);
s.Push(4);
cout << s.top() << endl;
s.Push(5);
s.Push(6);
cout << s.top() << endl;
s.Pop();
cout << s.top() << endl;
s.Pop();
cout << s.top() << endl;
s.Pop();
cout << s.top() << endl;
s.Pop();
cout << s.top() << endl;
s.Pop();
cout << s.top() << endl;
//s.Destory();
}
int main()
{
Test();
return 0;
}
三、拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
Data(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Data(const Data& d) // 拷贝构造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
int main()
{
Data d1(2022, 11, 16);
Data d2(d1);
}
①拷贝构造函数也是一种重载(仅有形参的个数与类型不同)
②参数只有一个,即为类类型对象的引用,使用传值方式编译器会直接报错,因为会造成无穷递归调用。
Data(const Data d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
因为当用一个存在的对象来创建一个新对象时,编译器会自动调用拷贝构造函数来对新对象的初始化操作。
假设代码以值传递方式进行,传参期间需要生成一个临时空间,当对d1进行初始化后,将d1拷贝给d2时调用拷贝构造函数,此时d1发生值传递,d1将值传递给dd1,而此时需要调用将dd1拷贝给d1时又会调用拷贝构造函数,此时dd1又发生值传递,dd1将值传递给ddd1……
我们发现值传递会发生形参实例化,类类型实例化,会在进行调用拷贝构造函数,就会一直调用,因此结果为无穷递归。
③若没有显示定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象内存存储按字节完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝
编译器不一定会生成默认的拷贝构造函数
我们再看一个对于Stack栈的例子。
来看下面这部分关于栈的代码,在不显示定义拷贝构造函数的情况下,我们利用s1来进行对s2的拷贝构造,编译器自动调用了默认的拷贝构造(其实通过汇编可以看到也就是一些MOV指令)来进行的浅拷贝。
这里我们就可以看到s1与s2竟然有着同样的地址空间,那么这个所谓的浅拷贝就是完完全全的按内存字节将一个类对象的所有内容拷贝到了新生成的对象本体中。
紧接着执行类中的析构函数:
发生中断! 我们都知道,析构函数的特点,也是在栈中进行的,先构造的后析构,后构造的先析构,对于这里我们先进行对s2对象的析构,之后我们在对s1进行析构,发生报错。
原因也不言而喻了,同一块内存空间,竟然想要进行多次释放?