构造函数
1.引入
class stu
{
public:
void Init_Num()
{
_num = 0;
}
int _num;
};
int main()
{
stu a;
a.Init_Num();
a._num++;
cout << a._num << endl;
return 0;
}
类似代码,初始化是必须的,忘了初始化会引起不必要的麻烦,为保证数据成员都有一个初始值,c++创立构造函数.构造函数并非是开辟空间,而是初始化对象,在对象的生命周期里只会调用一次
class stu
{
public:
stu()
{
_num = 0;
}
int _num;
};
int main()
{
stu a;
a._num++;
cout << a._num << endl;
return 0;
}
构造函数名字与类名相同,无返回值类型,这里调用是在创建对象的同时已经进行初始化,为什么不用如下形式呢?
stu a();
上面形式也可以是定义函数,加上括号这种会使编译器无法辨别你在定义函数a还是在创建对象a
构造函数传参和调用如下:
class stu
{
public:
stu(int n)
{
_num = n;
}
int _num;
};
int main()
{
stu a(0);
a._num++;
cout << a._num << endl;
return 0;
}
2.特性
(1)函数名与类名相同
(2)无返回值
(3)对象实例化时编译器会自动调用构造函数
(4)构造函数可以重载
如下:
using namespace std;
class stu
{
public:
stu(int n)
{
_num = n;
}
stu()
{
_num = 0;
}
//这里可以用缺省参数来简化
/*
stu(int n = 0)
{
_num = n;
}
*/
int _num;
};
int main()
{
stu a(3);//含参构造函数调用
stu b;//含无参构造函数调用
cout << a._num << endl;
cout << b._num << endl;
return 0;
}
析构函数
析构函数: 并非是对对象的销毁,而是在销毁时调用析构函数完成对对象资源的清理
1.特性
(1)析构函数名是在类名前加"~"
(2)无参数无返回值
(3)一个类只有一个析构函数,不能重载
(4)对象生命周期结束,会自动调用析构函数
class room
{
public:
room(int n)
{
_name = (char*)malloc(sizeof(char) * n);
_num = 0;
_space = 0;
}
~room()
{
free(_name);
}
private:
int _num;
int _space;
char* _name;
};
int main()
{
room a(10);
return 0;
}
上图所示,申请一个动态的_name数组,可是申请空间最害怕忘记释放而内存泄漏,或者觉得释放麻烦,析构函数就会在对象生命周期结束之前,进入析构函数(上图的~room函数(析构函数)对申请空间进行释放)
默认成员函数
默认成员函数:用户不用实现,编译器会自动生成的成员函数叫作默认成员函数
拿构造函数来说,编译器却不总会初始化,如下:
class room
{
public:
void Print()
{
cout << _num << endl;
cout << _space << endl;
}
int _num;
int _space;
};
int main()
{
room a;
a.Print();//此处打印皆是随机值,可是编译器不是初始化了嘛
return 0;
}
c++把类型分为内置类型和自定义类型,内置类型可以理解为自带的类型,如int,char…也包括指针,对于内置类型,在默认构造函数中不进行初始化,而对于自定义类型会自动调用他的默认构造函数(不用传参数的构造->无参的,全缺省的,编译器自动生成的),如下:
class room
{
public:
room(int num = 0,int space = 0)
{
_num = num;
_space = space;
}
int _num;
int _space;
};
class tem
{
room a;
room b;
};
int main()
{
tem t;//实例化类tem,tem内部的类room a和room b(属于自定义类型),会调用类room的默认构造函数,初始化a和b
//如下图
return 0;
}

析构函数在这里与构造函数类似,默认生成析构函数,对于内置类型成员不做处理,对于自定义类型成员会调用他的析构函数
class room
{
public:
room(int num = 0,int space = 0)//构造函数
{
_num = num;
_space = space;
}
~room()//析构函数
{
cout << "~room" << endl;
}
int _num;
int _space;
};
class tem
{
room a;
room b;
};
int main()
{
tem t;
return 0;
}
此处会打印两个~room,调用两次析构函数,在tem的默认析构函数会对类对象a和b调用他们的析构函数
上文提过对于内置类型,编译器是不进行处理的,如下:
class room
{
public:
room(int num = 0,int space = 0)
{
_num = num;
_space = space;
}
~room()
{
cout << "~room" << endl;
}
int _num;
int _space;
int s;//此处新添的变量s没有在构造函数被初始化
};
class tem
{
room a;
room b;
};
int main()
{
tem t;
return 0;
}
如下:

要想对这里的s进行初始化,可以在类room中s的声明位置给出缺省值,如下:
class room
{
public:
room(int num = 0,int space = 0)
{
_num = num;
_space = space;
}
~room()
{
cout << "~room" << endl;
}
int _num;
int _space;
int s = 0;//给出缺省值
};
给出缺省值,s被初始化

默认生成拷贝构造和赋值重载:
- 内置类型浅拷贝,一个一个字节拷贝
- 自定义类型,去调用成员的拷贝构造或赋值重载
拷贝构造
拷贝构造函数是一种特殊的构造函数.只有一个形参,是这个类类型的对象的引用
1.特征
- 拷贝函数是构造函数一种重载形式
- 拷贝构造函数的参数只有一个且必须是这个类类型对象的引用,如果采用传值传参的方式会报错
class room
{
public:
room(int num = 0)
{
_num = num;
}
room(room tem)//这里采用传值传参会报错,会造成无限,
{
_num = tem._num;
}
int _num;
};
int main()
{
room a(1);
room b(a);
return 0;
}
- 编译器对于内置类型,传参通常采用值传递的方式,在函数调用时,编译器会创建一个新的内存空间来存储传入的参数,并将实际参数的值复制到这个新的内存空间中;对于自定义类型,需要调用拷贝构造函数
内置类型采用浅拷贝,如果自定义类型采用浅拷贝,会导致新数据与原来的数据公用一块区域,并非各自拥有独立的空间
如果自定义类型采用传值传参的方式,如下:
class room
{
public:
room(int num = 0)
{
_num = num;
}
room(room tem)//报错
{
_num = tem._num;
}
int _num;
};
int main()
{
room a(1);
room b(a);
return 0;
}
出现上述情况会造成无穷递归,你在将实参传向形参的过程中,因为传的是自定义类型,采用拷贝构造,而拷贝构造又需要传参,传参又是自定义类型,又需要拷贝构造,而拷贝构造又需要传参,传参还是自定义类型,这样一直无穷拷贝,形成无限递归的情况
什么情况下需要实现拷贝构造?
自己实现析构函数释放空间,就需要实现拷贝构造
赋值运算符重载
1.运算符重载
函数声明:
返回值类型 operator操作符(参数)
如下,
class room
{
public:
room(int num = 0)
{
_num = num;
}
room(room& tem)
{
_num = tem._num;
}
int _num;
};
bool operator>(const room& x, const room& y)
//bool值作为返回值,operator>,">"是操作符
{
return x._num > y._num;//为真返回1(布尔类型性质,真为1,假为0),为假返回0
}
int main()
{
room a(1);
room b(0);
cout << (operator>(a, b)) << endl;
cout << (a > b) << endl;
/*
两种表示方式(operator>(a, b))和(a > b),个人喜欢第二种,第一种感觉就行正常调用函数,只是命名后可以加
操作符,大家根据个人爱好选择
*/
return 0;
}
- operator后面加的是操作符,不要加其他的符号,如#,@
- 参数必须有一个类类型的参数
当数据对对外不能访问,只能在类内部实现时:
class room
{
public:
room(int num = 0)
{
_num = num;
}
room(room& tem)
{
_num = tem._num;
}
bool operator>(const room& y)
{
return this->_num > y._num;//使用this指针更好的理解_num是当前对象
//return _num > y._num;(省略this指针)
}
private:
int _num;
};
int main()
{
room a(1);
room b(0);
cout << (a.operator>(b)) << endl;
cout << (a > b) << endl;
return 0;
}
.*
::
sizeof
?:
.
以上五个操作符不可重载
2.赋值运算符重载
class room
{
public:
room(int num = 0)
{
_num = num;
}
room(room& tem)
{
_num = tem._num;
}
/*
赋值运算符重载如下:
参数类型和返回值类型建议都用引用,之前说过,引用可以避免传值传参开辟新空间接收而浪费资源,返回值同理
*/
room& operator=(const room& t)
{
_num = t._num;
return *this;//返回值使用*this也就是该类room,是为了应对连续赋值的情况
//例如:a=b=c,返回值与接收类型不同会使赋值失败
}
void Print()
{
cout << _num << endl;
}
private:
int _num;
};
int main()
{
room a(1);
room b(0);
b.Print();
b = a;
b.Print();
return 0;
}
文章详细介绍了C++中的构造函数、析构函数、默认成员函数,以及拷贝构造和赋值运算符的重载,强调了它们在对象初始化、内存管理以及浅拷贝的重要性。

被折叠的 条评论
为什么被折叠?



