C++面向对象基础-构造函数
2023-12-31 15:31:23
1、构造函数
1.1?基本使用
构造函数是一种特殊的成员函数,用于创建对象,写法上有以下要求:
- 函数名必须与类名完全相同
- 构造函数不写返回值
- 如果程序员不手动编写构造函数,编译器就会自动添加一个无参的构造函数
手动添加构造函数,编译器就不会自动添加构造函数。
-
- 构造函数在创建对象时,常用于给对象的属性赋予初始值。
#include?<iostream>
using namespace std;
//?帕斯卡命名法(大驼峰命名法)
//?每个单词首字母大写
class MobilePhone
{
private: //?私有权限,最封闭的权限,只能在类内访问
string?brand; //?品牌
string?modle; //?型号
int?weight; //?重量
public: //?权限:public是最开放的权限。
MobilePhone() //?默认构造函数
{
????????brand?= "8848";
????????modle?= "M6?巅峰版";
????????weight?= 500;
}
//?品牌的get函数
string get_brand()
{
return?brand;
}
//?型号的get函数
string get_modle()
{
return?modle;
}
//?重量的get函数
int get_weight()
{
return?weight;
}
//?品牌的set函数
void set_brand(string?b)
{
????????brand?=?b;
}
};
int main()
{
//?栈内存对象
MobilePhone?mp1;
????cout?<<?mp1.get_brand() <<?endl;
????cout?<<?mp1.get_modle() <<?endl;
????cout?<<?mp1.get_weight() <<?endl;
return 0;
}
-
- 构造函数也支持函数重载和函数参数默认值
#include?<iostream>
using namespace std;
//?帕斯卡命名法(大驼峰命名法)
//?每个单词首字母大写
class MobilePhone
{
private: //?私有权限,最封闭的权限,只能在类内访问
string?brand; //?品牌
string?modle; //?型号
int?weight; //?重量
public: //?权限:public是最开放的权限。
MobilePhone() //?默认构造函数
{
????????brand?= "8848";
????????modle?= "M6?巅峰版";
????????weight?= 500;
}
//?有参构造函数(函数重载)
MobilePhone(string?b,string?m,int?w)
{
????????brand?=?b;
????????modle?=?m;
????????weight?=?w;
}
//?品牌的get函数
string get_brand()
{
return?brand;
}
//?型号的get函数
string get_modle()
{
return?modle;
}
//?重量的get函数
int get_weight()
{
return?weight;
}
//?品牌的set函数
void set_brand(string?b)
{
????????brand?=?b;
}
};
int main()
{
//?栈内存对象
MobilePhone mp1("vivo","iqoo12",300);
????cout?<<?mp1.get_brand() <<?endl;
????cout?<<?mp1.get_modle() <<?endl;
????cout?<<?mp1.get_weight() <<?endl;
return 0;
}
在创建对象的时候,必须调用任意构造函数。
-
- 函数参数默认值(全缺省默认构造函数)
- 缺省部分往前写
#include?<iostream>
using namespace std;
//?帕斯卡命名法(大驼峰命名法)
//?每个单词首字母大写
class MobilePhone
{
private: //?私有权限,最封闭的权限,只能在类内访问
string?brand; //?品牌
string?modle; //?型号
int?weight; //?重量
public: //?权限:public是最开放的权限。
MobilePhone() //?默认构造函数
{
????????brand?= "8848";
????????modle?= "M6?巅峰版";
????????weight?= 500;
}
//?有参构造函数
MobilePhone(string?b,string?m,int?w)
{
????????brand?=?b;
????????modle?=?m;
????????weight?=?w;
}
MobilePhone(int?w,string?b?= "oppo",string?m?= "findx")
{
????????brand?=?b;
????????modle?=?m;
????????weight?=?w;
}
//?品牌的get函数
string get_brand()
{
return?brand;
}
//?型号的get函数
string get_modle()
{
return?modle;
}
//?重量的get函数
int get_weight()
{
return?weight;
}
//?品牌的set函数
void set_brand(string?b)
{
????????brand?=?b;
}
};
int main()
{
//?栈内存对象
MobilePhone?mp1;
????cout?<<?mp1.get_brand() <<?endl;
????cout?<<?mp1.get_modle() <<?endl;
????cout?<<?mp1.get_weight() <<?endl;
????MobilePhone?*mp2?= new MobilePhone();
return 0;
}
1.2?构造初始化列表
构造初始化列表是一种更简单的给成员变量赋予初始值的写法。传参遵寻就近原则。
注意:当构造函数的局部变量与成员变量同名时,除了使用后面学习的this指针外,也可以使用构造初始化列表的方式进行区分。
#include?<iostream>
using namespace std;
//?帕斯卡命名法(大驼峰命名法)
//?每个单词首字母大写
class MobilePhone
{
private: //?私有权限,最封闭的权限,只能在类内访问
string?brand; //?品牌
string?modle; //?型号
int?weight; //?重量
public: //?权限:public是最开放的权限。
//?构造初始化列表:编译器成员变量与局部变量可以做区分
MobilePhone()://直接赋值,成员变量已经创建。
brand("8848"),modle("M6?巅峰版"),weight(500){}
MobilePhone(string?brand,string?modle,int?weight)://默认传参
brand(brand),modle(modle),weight(weight){}
//对象创造之前被调用,还没有创造对象,成员变量已经创建。不能放到函数体内,函数无法识别局部变量与成员变量
//?品牌的get函数
string get_brand()
{
return?brand;
}
//?型号的get函数
string get_modle()
{
return?modle;
}
//?重量的get函数
int get_weight()
{
return?weight;
}
};
int main()
{
//?栈内存对象
MobilePhone?mp1;
????cout?<<?mp1.get_brand() <<?endl;
????cout?<<?mp1.get_modle() <<?endl;
????cout?<<?mp1.get_weight() <<?endl;
????MobilePhone?*mp2?= new MobilePhone("三星","note7",100);
????cout?<<?mp2->get_brand() <<?endl;
????cout?<<?mp2->get_modle() <<?endl;
????cout?<<?mp2->get_weight() <<?endl;
delete?mp2;
????mp2?=?NULL;
return 0;
}
1.3?隐式调用构造函数
截至到目前为止,对于构造函数的调用都是显式调用的。
#include?<iostream>
using namespace std;
class Test
{
private:
int?val;
public:
Test(int?v,int?i,string?str,double?d)
{
????????val?=?v;
????????cout?<< "构造函数" <<?endl;
}
int get_val()
{
????????cout?<< &val?<<?endl;
return?val;
}
};
int main()
{
//????Test?t1(2);??//?显式调用构造函数
//????cout?<<?t1.get_val()?<<?endl;
Test?t2?= {123,2,"string",2.4}; //?隐式调用构造函数,多参数传递C++11支持
????cout?<<?t2.get_val() <<?endl;
return 0;
}
建议使用隐式调用,可以使用explicit关键字屏蔽隐式调用语法。
1.4?拷贝构造函数
1.4.1?概念
当程序员不手写拷贝构造函数时,编译器会自动添加一个拷贝构造函数,使对象创建可以通过这个构造函数进行实现。
#include?<iostream>
using namespace std;
class Test
{
private:
int?val;
public:
Test(int?v)
{
????????val?=?v;
????????cout?<< "构造函数" <<?endl;
}
//?手动添加默认的拷贝构造函数
Test(Test?&t)
{
????????val?=?t.val;
}
int get_val()
{
return?val;
}
};
int main()
{
Test t1(4);
????cout?<<?t1.get_val() <<?endl;
Test t2(t1); //?调用拷贝构造函数
????cout?<<?t2.get_val() <<?endl;
return 0;
}
没有任何构造函数的时候:编译器会给我们创建一个默认构造函数、拷贝构造函数。
如果我们有一个默认构造函数,编译器不会创建默认构造函数,会创建拷贝构造函数。
如果我们有一个有参构造函数,编译器不会创建默认构造函数,会创建拷贝构造函数。
如果我们有一个拷贝构造函数,编译器不会创建默认构造函数,不会创建拷贝构造函数。
【思考】:拷贝构造函数会存在隐患吗?
存在,当成员变量出现指针类型时,默认的拷贝构造函数会导致两个对象的成员变量指向同一处,改变一处另一处也会改变不符合面向对象的设计规范,这种现象被称为”浅拷贝“。
1.4.2?浅拷贝
存在的问题:更改一处三处都会改变
#include?<iostream>
#include?<string.h>
using namespace std;
class Dog
{
private:
char *name;
public:
Dog(char *n)
{
????????name?=?n;
}
void show_name()
{
????????cout?<<?name?<<?endl;
}
};
int main()
{
char?arr[20] = "旺财";
Dog d1(arr);
Dog d2(d1); //?调用的是拷贝构造函数,不调用构造函数
strcpy(arr,"大黄");//两个都会改变
????d1.show_name(); //?大黄
????d2.show_name(); //?大黄
return 0;
}
这种情况必须要手写构造函数,使每次赋值都创建一个新的副本,从而每个对象单独持有自己的成员变量,这种方式被称为“深拷贝”。
1.4.3?深拷贝
new开辟的空间无法释放,造成内存泄漏的问题。
#include?<iostream>
#include?<string.h>
using namespace std;
class Dog
{
private:
char *name;
public:
Dog(char *n)
{
????????name?= new char[20];
strcpy(name,n);
}
Dog(Dog?&d)
{
???????name?= new char[20];
strcpy(name,d.name);
}
void show_name()
{
????????cout?<<?name?<<?endl;
}
};
int main()
{
char?arr[20] = "旺财";
Dog d1(arr);
Dog d2(d1); //?拷贝构造函数
strcpy(arr,"大黄");
????d1.show_name(); //?旺财
????d2.show_name(); //?旺财
return 0;
}
#include?<iostream>
#include?<string.h>
using namespace?std;
class Dog
{
private:
char *name;
public:
//?构造函数,初始化对象的name成员变量
Dog(char *n)
{
????????name?=?n; //?将传入的指针赋值给name,这会导致问题后面会解释
}
//?拷贝构造函数,用于创建一个新的对象并复制已有对象的数据
Dog(Dog?&d)
{
????????name?= new char[20]; //?为name分配内存空间
strcpy(name,?d.name); //?复制参数对象的name值到当前对象的name
}
//?打印狗的名字
void show_name()
{
????????cout?<<?name?<<?endl;
}
};
int main()
{
char?arr[20] = "旺财"; //?创建一个字符数组,并存储字符串"旺财"
????Dog?d1(arr); //?创建一个Dog对象d1,传入arr作为参数
????Dog?d2(d1); //?使用拷贝构造函数,创建一个新的Dog对象d2,复制d1的数据
strcpy(arr, "大黄"); //?修改arr中的字符串为"大黄"
????d1.show_name(); //?输出:旺财,因为d1的构造函数只是将name指针指向arr的地址,所以修改arr后,d1的name也改变了
????d2.show_name(); //?输出:旺财,因为拷贝构造函数在创建d2时复制了d1的name,所以不受arr的修改影响
return 0;
}
- 创建有参构造函数,编译器不会创建无参构造函数,会创建拷贝构造函数。
- 创建无参构造函数,编译器不会创建无参构造函数,会创建拷贝构造函数。
- 创建拷贝构造函数,编译器不会创建无参构造函数,不会创建拷贝构造函数。
【思考】?深拷贝的代码是否存在隐患?
存在,new开辟的空间无法释放,造成内存泄漏的问题。
文章来源:https://blog.csdn.net/m0_74937538/article/details/135315908
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!