C++类和对象2
有些时候我们刚开始接触c++的时候会发现一些这样的用法例如:
class a
{
};
这显然是一个空类,那么这样的一个空类有什么用,或者说这样的一个空类只是我们所看到的那样内容为空吗? 答案是否定的。那么这样的一个空类它包含了什么样的隐藏秘密呢?
答案是一个简单的空类它其实包含了6个默认的成员函数:分别为构造函数,析构函数,拷贝构造,赋值重载,普通对象取地址,和const修饰对象取地址。
接下来我们分别进行说明这几类函数的作用和使用。
目录
一.构造函数
1.构造函数的定义:
是一个
特殊的成员函数,名字与类名相同
,
创建类类型对象时由编译器自动调用
,保证每个数据成员都有 一个合适的初始值,并且
在对象的生命周期内只调用一次
。
其实构造函数的作用就是完成成员变量的初始化
,但不同于c语言的初始化构造函数可以实在创造对象的同时就完成成员变量的初始化。
2.构造函数的特征:
函数名与类名相同。
无返回值。
对象实例化时编译器
自动调用
对应的构造函数。
构造函数可以重载。
3.构造函数的实现:
3.1.系统默认的构造函数
3.2无参构造
3.3 带参构造
这里出一个问题对于代码风格造成的问题:成员变量year最后的结果是多少呢?
class A
{
public:
A(int year)
{
year = year;
}
private:
int year;
};
int main()
{
A a(20);
}
答案是:随机值。那么为什么是随机值呢?这里主要是变量之间它采用了就近原则,所以等式左边的year会直接寻找离他最近的变量所以会将等式右边的year直接赋值给它自己,所以year最后的值就是随机值。
我们继续来说带参的构造函数,我们一般推荐使用的是全缺省的构造函数(注:
)
二 析构函数
构造函数时完成对象的初始化,那么一个对象又是怎么样被销毁的呢?
1.析构函数的定义
2.析构函数的特征
析构函数名是在类名前加上字符
~
。
无参数无返回值。
。
对象生命周期结束时,
C++
编译系统系统自动调用析构函数。
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int)*capacity);
if (_a == nullptr)
{
cout << "malloc fail" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
//析构函数的实现
~Stack()
{
// 像Stack这样的类,对象中的资源需要清理工作,就用析构函数
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
这里是完成构造函数,有自己定义的析构函数的效果。同构造函数一样对于内置成员变量析构函数会置为随机值,而自定义类型则会去调用他们的析构函数。
三 拷贝函数
如果某些时候我们需要去复制一个对象,这样的话我们该怎么样去解决呢?
这里我们就需要引入拷贝函数。那么什么叫做拷贝函数呢?我们应该去怎么实现呢?有什么注意事项呢?这里我们一一来说道。
1.拷贝函数定义
:
只有单个形参
,该形参是对本
类类型对象的引用
(
一般常用
const
修饰
)
,在用
已存在的类类型对象
。
2.拷贝函数的特性
拷贝构造函数
是构造函数的一个重载形式
。
。
若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷
3.拷贝函数的实现
class A
{
public:
A()
{
_a1 = 1;
_a2 = 2;
}
~A()
{
cout << "A()" << endl;
}
private:
int _a1;
int _a2;
};
class Data
{
public:
/*Data()
{
_year = 2021;
_month = 12;
_day = 12;
}*/
//Data(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
Data(int year = 2022,
int month = 12,
int day = 12)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
A a;
};
int main()
{
Data s;
//拷贝函数的调用
Data s2(s);
return 0;
}
调用系统默认生成拷贝函数(注:这里拷贝函数的拷贝对自定义类型和内置类型的成员变量处理都是一致的完成字节序的值拷贝)
图1 调用系统默认生成的拷贝函数 图2 调用用户自己定义的拷贝函数
在这里我们顺便说一下在自定义拷贝函数的时候一定要使用引用不然会出现无限递归例如 Data(Data s){}正确的使用是Data (const Data & s){}其中const是为了保护原数据不被轻易改动。
class A
{
public:
A()
{
_a1 = 1;
_a2 = 2;
}
~A()
{
cout << "A()" << endl;
}
private:
int _a1;
int _a2;
};
class Data
{
public:
/*Data()
{
_year = 2021;
_month = 12;
_day = 12;
}*/
//Data(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
Data( const Data &s)
{
_year = s._year;
_month = s._month;
_day = s._day;
}
Data(int year = 2023,
int month = 12,
int day = 12)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
A a;
};
int main()
{
Data s;
//拷贝函数的调用
Data s2(s);
return 0;
}
我们可以发现s2均完整的赋值了s的内容,但是这里真的就没有问题了吗?如果我们使用系统默认生成的拷贝函数成员变量中含有指针那么会出现什么样的问题呢?
class String
{
public:
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
int main()
{
String s;
String s1(s);
}
我们可以看到虽然虽然s1拷贝了s的内容但是最后系统还是抛出了错误那么这个错误来自那里呢?
我们看这幅图
这里就是我们之前说的系统默认生成的拷贝函数是浅拷贝,那么怎么去完成深拷贝我们后边在继续讲解。