理想的状态是创建初对象时,程序员可以指定初始值,这样可以创建出属性不同的对象
构造函数可以使对象在创建之初就具备了自定义的属性值,而不是每次创建出一样的对象
1、构造函数的概念
构造函数是类中一种特殊的函数,构造函数的功能有:
● 创建对象
● 对属性初始化
如果程序员不写构造函数,编译器会自动添加一个没有参数的、函数体为空的构造函数
如果程序员手动编写任意一个构造函数,编译器不再为类内添加任何构造函数
部分代码
// 构造函数
MobilePhone(){} // 如果不写,编译器自动添加
MobilePhone mp1; // 在此处默认调用
MobilePhone* mp2 = new MobilePhone; // 在new MobilePhone处默认调用
构造函数是一种特殊的函数,不写返回值类型,因为返回值类型都是创建的对象本身。函数名称与类名完全一致。
2 、构造函数的重载
构造函数支持重载,如果程序员手动编写任意一个构造函数,编译器将不在为类添加任何构造函数。
此时如果仍要调用无参的构造函数且手写的不是无参构造函数时 ,则需要利用构造函数重载来实现多个构造函数。
构造函数支持参数默认值
构造函数重载示例
#include <iostream>
using namespace std;
class MobilePhone
{
private:
string brand;
string model;
int weight;
public:
MobilePhone(string b,string m,int w) // 重载的构造函数
{
brand = b;
model = m;
weight = w;
}
MobilePhone() // 构造函数参数默认值
{
brand = "山寨";
model = "8848";
weight = 188;
}
void show()
{
cout << brand << " " << model << " " << weight << endl;
}
};
int main()
{
// 调用三个参数的构造函数
MobilePhone mp1("小米","13",188);
mp1.show(); // 小米 13 188
MobilePhone* mp2 = new MobilePhone("苹果","14",177);
mp2->show();
delete mp2; // 苹果 14 177
MobilePhone mp3;
mp3.show(); // 山寨 9948 188
return 0;
}
构造函数也支持默认值
3 、构造初始化列表
构造初始化列表是在构造函数中给成员变量赋值的简便写法,可以根据实际情况决定是否采用。
构造函数初始化列表三种形式
格式1:
// 在构造函数的函数体中赋值
MobilePhone(string b, string m, int w)
{
brand = b;
model = m;
weight = w;
}
格式2:
// 构造初始化列表
MobilePhone(string b, string m, int w):brand(b), model(m), weight{w}{}
//以上两种写法基本相同,存在部分区别
//1.对常成员变量赋值------------const关键字用法时详细讲解
如果成员变量是const修饰的,不能使用构造函数的函数体赋值。
//2.执行时机--------------------对象的创建和销毁流程时讲解
构造初始化列表要比函数体中赋值执行效率更高
格式3:
//如果构造初始化列表影响了代码的可读性,也可以考虑不使用或混合使用
MobilePhone(string b, string m, int w):brand(b), weight{w} //混用
{
model = m;
}
4 、隐式调用构造函数
隐式调用示例
#include <iostream>
using namespace std;
class Bird
{
private:
string name;
int age;
public:
Bird(string n)
{
name = n;
cout << "创建了一只鸟:" << name << endl;
}
Bird(int a,string n = "Empty")
{
age = a;
name = n;
cout << "创建了一只鸟:" << name << endl;
}
string get_name()
{
return name;
}
int get_age()
{
return age;
}
};
int main()
{
string name = "Tom";
// name的类型是string字符串类型,参数b的类型是Bird,本质上无直接联系
// 赋值过程中,编译器发现右边的字符串name能够作为左边的参数构造出来
// 编译器自动完成了对象b的创建并将name送进去
Bird b = name; // 隐式调用了构造函数
cout << b.get_name() << endl; // Tom
Bird b2 = 12; // 隐式调用了构造函数
cout << b2.get_name() << endl; // Empty
cout << b2.get_age() << endl; // 12
return 0;
}
执行结果
创建了一只鸟:Tom
Tom
创建了一只鸟:Empty
Empty
12
原因分析:
name
的类型是string
字符串类型,对象b
的类型是Bird
,本质上无直接联系
赋值过程中,编译器发现右边的字符串name
能够作为左边的参数构造出来
编译器帮程序员完成了对象b
的创建并将name
送进去
以上用法可能会无意中创建了对象
只需要给构造函数增加explicit
关键字 修饰,即可屏蔽隐式调用构造函数这种用法
隐式调用在C++使用较多——方便
显式调用在C++使用较少,主要在QT课程中使用
explicit 显式调用
#include <iostream>
using namespace std;
class Bird
{
private:
string name;
int age;
public:
explicit Bird(string n)
{
name = n;
cout << "创建了一只鸟:" << name << endl;
}
explicit Bird(int a, string n = "Empty")
{
age = a;
name = n;
cout << "创建了一只" << age << "岁的鸟:" << name << endl;
}
string get_name()
{
return name;
}
int get_age()
{
return age;
}
};
int main()
{
string name = "Tom";
// Bird b = name; 错误
Bird b(name); // 显示调用构造函数
cout << b.get_name() << endl;
// Bird b2 = 12; 错误
Bird b2(12); // 显示调用构造函数
cout << b2.get_name() << endl;
cout << b2.get_age() << endl;
return 0;
}
5、 拷贝构造函数
克隆、复制粘贴
编译器会自动添加一个拷贝构造函数
拷贝构造函数也是一个重载的构造函数
5.1 拷贝构造函数的概念
程序员在一个类中不写拷贝构造函数时,编译器会自动添加一个拷贝构造函数,与其它的构造函数一样支持重载,用于对象的拷贝构建
拷贝构造函数创建出的对象仅仅与原对象数据相同,对象之间相互独立,修改其中任何一个对另外一个都没有任何影响
示例
void show()
{
cout << brand << " " << model << " " << weight << endl;
cout << &brand << " " << &model << " " << &weight << endl;
}
MobilePhone mp1("小米", "13", 188);
mp1.show();
//小米 13 188
//地址1 地址2 地址3
MobilePhone mp2(mp1); //拷贝构造函数
mp2.show();
//小米 13 188
//地址4 地址5 地址6
// map2与mp1是两个对象,相互数据独立
5.2 浅拷贝
默认的拷贝构造函数仅仅把两个对象的属性值进行了赋值操作,当一个类中的成员变量出现了指针类型,默认的拷贝构造函数会出现浅拷贝问题
直接赋值操作会导致两个指针保存的地址相同,指向同一份内存,这不符合面向对象的特性
#include <iostream>
#include <string.h>
using namespace std;
class Dog
{
private:
char* name; // 指针
public:
Dog(char* n)
{
name = n; //字符数组n赋给name,name为char *类型指针,指向字符数组首地址c
}
// 编译器自动添加拷贝构造函数------拷贝构造函数也是一个重载的构造函数
Dog(const Dog& d) // d是引用,是d1的别名,相当于将d1传递进来
{
name = d.name; // d1的name赋给了d2的名字,二者均为指针,指针拷贝的是指向的地址
// 故:新创建的地址也是c
}
void show_name()
{
cout << name << endl;
}
};
int main()
{
char c[20] = "wangcai";
Dog d1(c); // 字符数组c传递到构造函数d1中
Dog d2(d1); // 此时调用默认的拷贝构造函数
strcpy(c,"xiaobai");
d1.show_name(); // xiaobai---与面向对象相悖
d2.show_name(); // xiaobai---与面向对象相悖
return 0;
}
对象的封装目的是让对象独享这一块私有的数据,不应该受到其它对象或外界因素的影响
浅拷贝代码中出现了使用指针带来的问题(浅拷贝)
如果非要用指针怎么解决这种问题?
引入深拷贝 ↓
5.3 深拷贝
深拷贝为解决浅拷贝资源不独占的问题
思路:每次指针赋值时,单独开辟内存空间,让当前指针独享(独自去指向)
拓展:字符数组支持在C++中以new的方式创建堆内存
解决浅拷贝资源不独占问题,但造成内存泄漏
public:
Dog(char* n)
{
//开启一个堆内存区域
name = new char[20]; // 创建一个char类型数组对象,让name去指向独享区
strcpy(name, n); // 拷贝数据
}
Dog(const Dog& d)
{
//开启一个堆内存区域
name = new char[20]; // 创建一个char类型数组对象,让name去指向独享区
strcpy(name, d.name); // 拷贝数据
}
【思考】以上深拷贝代码有无问题?
有new
开辟的空间就必须有delete
释放空间,会造成内存泄漏(类似malloc
和free
)
引入析构函数 ↓