目录
构造函数
构造函数的基本使用
构造函数也支持函数重载
构造函数也支持函数参数默认值
构造初始化列表
拷贝构造函数
浅拷贝和深拷贝
析构函数
总结
练习一下ヽ( ̄▽ ̄)ノ
构造函数
构造函数的基本使用
构造函数是一种特殊的成员函数,用于创建对象时初始化,写法上有以下要求:
● 函数名称必须与类名完全一样。
● 构造函数不写返回值
● 如果程序员不手动编写构造函数,编译器就会自动添加一个默认无参数的构造函数。
● 手动添加构造函数后,编译器就不会自动添加默认无参构造函数。
class Car
{
private://权限:最私有的权限string brand; // 品牌string modle; // 型号int weight; // 重量
public: // 权限:最开放的权限Car(string b,string m,int w) //手动添加的构造函数{brand=b;modle=m;weight=w;}string get_brand() //外部函数接口{return brand;}string get_modle(){return modle;}int get_weight(){return weight;}
};
int main()
{Car *myCar = new Car; // 创建堆内存对象//cout << Car.brand << endl; //错误,brand是私有成员 不能外部访问cont << Car.get_brand << endl; //可以使用预留的接口访问
}
构造函数也支持函数重载
class Car
{
private://权限:最私有的权限string brand; // 品牌string modle; // 型号int weight; // 重量
public: // 权限:最开放的权限Car(string b,string m,int w) // 有参构造函数{brand=b;modle=m;weight=w;}Car() // 无参构造函数{brand="xiaomi";modle="su7";weight=1500;}string get_brand() // 外部函数接口{return brand;}string get_modle(){return modle;}int get_weight(){return weight;}
};
int main()
{Car *myCar = new Car; // 调用无参构造函数Car *myCar2 = new Car("HuaWei","问界","2000"); // 此时调用有参构造函数}
构造函数也支持函数参数默认值
从第一个设置默认值的变量开始,在其变量之后的所有参数都要加默认值,在他之前的变量可以不加默认值。
class Car
{
private://权限:最私有的权限string brand; // 品牌string modle; // 型号int weight; // 重量
public: // 权限:最开放的权限Car(string b="xiaomi",string m="su7",int w=1500) // 全缺省时,不能和无参构造函数同时存在{brand=b;modle=m;weight=w;}// Car() // 无参构造函数// {// brand="xiaomi";// modle="su7";// weight=1500;// }string get_brand() // 外部函数接口{return brand;}string get_modle(){return modle;}int get_weight(){return weight;}
};
int main()
{Car *myCar = new Car; // 全缺省Car *myCar2 = new Car("问界","M7","2000"); // 此时调用有参构造函数}
构造初始化列表
当构造函数的局部变量与成员变量重名时可以使用构造初始列表的方式区分,此外,使用构造初始化列表还可以给被 const 修饰的成员变量赋值。
class Car
{
private://权限:最私有的权限const string brand; // 品牌string modle; // 型号int weight; // 重量
public: // 权限:最开放的权限Car(string b,string modle,int w):brand(b),modle(modle),weight(w){} // 用构造初始化列表的方式赋值string get_brand() // 外部函数接口{return brand;}string get_modle(){return modle;}int get_weight(){return weight;}
};
int main()
{Car *myCar = new Car("问界","M7","2000"); // 此时调用有参构造函数}
拷贝构造函数
C++的拷贝构造函数是一种特殊的成员函数,用于创建一个对象的副本。它的参数是一个对象的引用,通过这个参数可以将一个对象的值复制给另一个对象。
拷贝构造函数通常在以下情况下被调用:
1、当用一个对象初始化另一个对象时,会调用拷贝构造函数。例如:
class MyClass {
public:MyClass(const MyClass& obj) {// 拷贝构造函数的实现}
};MyClass obj1;
MyClass obj2 = obj1; // 调用拷贝构造函数
2、当将一个对象作为函数参数传递给函数时,会调用拷贝构造函数。例如:
void func(MyClass obj) {// 函数体
}MyClass obj;
func(obj); // 调用拷贝构造函数
3、当函数返回一个对象时,会调用拷贝构造函数。例如:
MyClass func() {MyClass obj;// 对 obj 进行初始化和操作return obj; // 调用拷贝构造函数
}
需要注意的是,默认情况下,C++会生成一个默认的拷贝构造函数,该函数会将对象的所有成员变量进行一一拷贝。但如果类中存在指针或其他资源,需要手动编写拷贝构造函数来处理这些资源的拷贝问题,以防止浅拷贝带来的问题。
浅拷贝和深拷贝
C++中的拷贝操作有浅拷贝和深拷贝两种方式。
浅拷贝是指拷贝对象时,只是简单地将一个对象的数据成员的值复制给另一个对象的对应数据成员,而不会复制指向动态分配内存的指针。这意味着两个指针将指向同一块内存,当其中一个对象释放这块内存时,另一个对象的指针将成为悬空指针。例如:
class MyClass {
public:int* data;MyClass(const MyClass& other) : data(other.data) {// 拷贝构造函数的实现}
};MyClass obj1;
obj1.data = new int(5);MyClass obj2 = obj1; // 浅拷贝delete obj1.data; // 释放内存
cout << *obj2.data; // 可能会输出无效的值
深拷贝是指拷贝对象时,除了复制数据成员的值外,还会为每个指针成员分配一块新的内存,并将源对象的值复制到新的内存中,以确保两个对象之间的指针成员指向不同的内存块。这样即使一个对象释放了内存,另一个对象的指针仍然有效。例如:
class MyClass {
public:int* data;MyClass(const MyClass& other) : data(new int(*other.data)) {// 拷贝构造函数的实现}~MyClass() {delete data;}
};MyClass obj1;
obj1.data = new int(5);MyClass obj2 = obj1; // 深拷贝delete obj1.data; // 释放内存
cout << *obj2.data; // 仍然可以正常输出
析构函数
C++中的析构函数是一种特殊的成员函数,用于在对象的生命周期结束时执行清理操作。析构函数的名称与类的名称相同,前面加上一个波浪号(~)作为前缀,没有返回类型,也没有参数。
析构函数在以下情况下被调用:
- 当对象的作用域结束时,例如,当对象在函数中定义并在函数结束时销毁。
- 当对象是另一个对象的成员,并且该对象的析构函数被调用时。
- 当使用delete关键字显式释放通过new关键字配分的内存时。
析构函数的主要目的是释放对象分配的资源,例如动态分配的内存、打开的文件等。它可以通过在析构函数中使用delete关键字来释放内存,或者通过关闭文件句柄等操作来释放资源。
以下是一个示例,展示了一个类的析构函数的基本用法:
class MyClass {
public:MyClass() {cout << "构造函数被调用" << endl;}~MyClass() {cout << "析构函数被调用" << endl;}
};int main() {MyClass obj; // 创建一个对象// 在此处执行其他操作return 0; // 对象的作用域结束,析构函数被调用
}
当对象的作用域结束时,析构函数将被自动调用,输出如下结果:
构造函数被调用
析构函数被调用
需要注意的是,如果类中使用了动态分配的内存或其他资源,在析构函数中应该对这些资源进行释放,以避免内存泄漏或资源泄漏的问题。
总结
构造函数 | 析构函数 |
创建对象时手动调用 | 当对象销毁时,自动调用 |
函数名称是类名 | 函数名称是~类名 |
构造函数可以重载 | 析构函数没有参数,不能重载 |
用于创建对象时并初始化 | 用于销毁对象时释放资源 |
有返回值但是不写,返回值是新创建的对象 | 没有返回值 |
练习一下ヽ( ̄▽ ̄)ノ
写一个Dog类,要求有性别、年龄和品种三个属性,属性值封装,使用构造函数传参初始化。增加函数Dog* birth(const Dog& d),在函数体内部判断d与当前狗对象的属性值,当满足以下条件时,返回新创建的狗对象:
● 两条狗的年龄2-5
● 一公一母
新创建的狗对象的属性满足以下条件:
● 年龄:1岁
● 性别:随意
● 品种:
○ 如果父母的品种一样,品种就是父母的品种
○ 如果父母的品种不一样,品种是父母品种的拼合(自己制定拼合逻辑,或者直接是父母的品种之一)
如果两条狗不能生育,返回NULL。
参考代码
#include <iostream>
#include<string.h>
#include<time.h>
using namespace std;class Dog
{
private:int sex; //0母 1公int age; //小于2或大于5不能生育char *variety=new char[20];public:Dog(int s,int a,char *v){if(s==3) // 性别随机{sex=time(NULL)%2;}else{sex=s;}age=a;strcpy(variety,v);}Dog* brith(const Dog &D){if(sex != D.sex){if( age < 2 || age > 5 || D.age < 2 || D.age > 5 ){return NULL;}Dog* NewDog = new Dog(3,1,D.variety);return NewDog;}return NULL;}int get_sex(){return sex;}int get_age(){return age;}char *get_variety(){return variety;}};int main()
{int sex,age;char variety[20];while(1){cout << "请输入狗A的 性别(0:母 1:公) 年龄 品种" << endl;cin >> sex >> age >> variety;Dog dogA(sex,age,variety);cout << "请输入狗B的 性别(0:母 1:公) 年龄 品种" << endl;cin >> sex >> age >> variety;Dog dogB(sex,age,variety);Dog* NewDog=dogB.brith(dogA);if(NewDog==NULL){cout << "dogA和dogB无法生育" << endl;continue;}cout << "小狗的属性:" << endl;cout << "sex:" << NewDog->get_sex() << endl;cout << "age:" << NewDog->get_age() << endl;cout << "variety:" << NewDog->get_variety() << endl;}return 0;
}