C++基础知识学习记录—构造函数
1、构造函数(constructor)
作用:用于创建对象时给属性值进行初始化
构造函数是一个特殊的函数:
1、构造函数与类同名
2、如果没有显式给出构造函数,编译器会给出默认的构造函数(参数为空,并且函数体也为空),并没有实际操作,如果给出任意的构造函数,系统默认的构造函数就不存在了。
3、构造函数是在创建对象的时候,自动调用的。
4、有返回值,但是不写返回值类型,也不可以写void
构造函数分为有参构造函数和无参构造函数,也就是说构造函数可以重载
有参的构造函数的形参,也支持默认参数
但函数重载和参数默认值不能同时使用,否则会出现错误
#include <iostream>
#include <string>using namespace std;class Goods
{
private:string name;string color;double price;public:/*编译器默认的构造函数 参数为空,函数体也为空goods(){}*///无参构造函数Goods(){name="上衣";color="白色";price = 119.9;}//有参构造函数 构造函数可以重载Goods(string n,string c,double p){name=n;color=c;price = p;}void show(){cout << color << "的" << name << "单价是" << price << endl;}
};
int main()
{Goods g1;//调用无参构造函数g1.show();Goods g2("裤子","黑色",99.9);//调用有参构造函数g2.show();return 0;
}
有参构造函数 形参支持默认参数Goods(string n="鞋子",string c="酒红色",double p=298.9){name=n;color=c;price = p;}
构造初始化列表
在现阶段,构造初始化列表是一种更简单的给成员变量赋予初始值的写法
如上面的构造函数可以简写为
Goods():name("上衣"),color("白色"),price(119.9){ }Goods(string name,string color,double price):name(name),color(color),price(price){ }
2、拷贝构造函数
当程序员不手写拷贝构造函数时,编译器会自动添加一个拷贝构造函数,用于实现对象的拷贝创建。
拷贝构造函数的特点:
1、拷贝构造函数与构造函数构成重载
2、如果不显式地写出拷贝构造函数,编译器会给出默认的拷贝构造函数,完成对象之间的值复制,如果显式地写出拷贝构造函数,编译器就不会提供默认的拷贝构造函数。
拷贝构造函数的形式:
1、与类同名
2、参数是对象的引用或者是const修饰对象的引用
3、拷贝构造函数是在拷贝对象时,自动调用
对象之间是相互独立的,对象之间的属性也是相互独立的。
#include <iostream>
#include <string>using namespace std;class Phone
{
private:string brand;//brand:品牌string model;double price;
public:Phone(string brand,string model,double price):brand(brand),model(model),price(price){ }//编译器默认的构造拷贝函数Phone(const Phone& iphone){brand=iphone.brand;model=iphone.model;price=iphone.price;}void show(){cout << "品牌:" << brand << " 型号:" << model;cout << " 价格:" << price << endl;}
};
int main()
{Phone P1("红米","K80Pro",2699.9);P1.show();Phone* P2 = new Phone("华为","mate60",5699.9);P2->show();delete P2;P2=NULL;Phone P3(P1);//面向对象的前提:每个对象单独持有一份自己的成员变量P3.show();return 0;
}
1、浅拷贝
编译器默认给出的拷贝构造函数,完成的就是浅拷贝,会完成对象之间简单的值复制。如果对象的属性有指针类型时,也只会简单的地址值复制,这两个对象的指针保存同一块地址,指向了同一块内存,破坏了对象之间的独立性,此时需要手写深拷贝。
#include <iostream>
#include <string>
#include <string.h>
using namespace std;class Phone
{
private:char* brand;
public:Phone(char* brand):brand(brand){ }//构造拷贝函数,如不自定义编译器有默认的,可以直接拿来用//Phone(const Phone& Ph)//{// brand=Ph.brand;//}void show(){cout << brand << endl;}char* get_brand(){return brand;}
};
int main()
{char ch[20]="Redmi";Phone P1(ch);Phone P2(P1);P1.show();P2.show();strcpy(ch,"xiaomi");P1.show();P2.show();strcpy(P1.get_brand(),"iPhone");P1.show();P2.show();strcpy(P2.get_brand(),"huawei");P1.show();P2.show();return 0;
}
上述代码,创建了一个手机类,两个手机类对象,P1和P2。直接修改字符串ch,P1和P2的型号 都会跟着改变;修改P1的型号,P2的型号也会变化;修改P2的型号,P1的型号也会改变。
这就是浅拷贝的隐患,当成员变量出现指针类型时,默认的拷贝构造函数会导致两个对象的成员变量指向同一处,不符合面向对象的设计规范,违背了对象的独立性
2、深拷贝
当对象属性有指针类型时,对象指针变量需要指向自己独立的区域,拷贝内容代替拷贝地址
#include <iostream>
#include <string>
#include <string.h>
using namespace std;class Phone
{
private:char* brand;
public:Phone(char* bra){brand = new char[20];strcpy(brand,bra);}Phone(const Phone& Ph){brand = new char[20];strcpy(brand,Ph.brand);}void show(){cout << brand << endl;}char* get_brand(){return brand;}
};int main()
{char ch[20]="Redmi";Phone P1(ch);Phone P2(P1);P1.show();P2.show();cout << endl;strcpy(ch,"xiaomi");P1.show();cout << "*********" << endl;P2.show();cout << endl;strcpy(P1.get_brand(),"iPhone");P1.show();cout << "*********" << endl;P2.show();cout << endl;strcpy(P2.get_brand(),"huawei");P1.show();cout << "*********" << endl;P2.show();return 0;
}
上述代码是深拷贝,结果就是:直接修改字符串ch,P1和P2的型号都不会变化;修改P1的型号,P2的型号不会变化;修改P2的型号,P1的型号不会改变。
3、隐式调用构造函数
等号赋值时,等号左侧是对象类型,右侧恰好是对象构造函数所需要的类型,这时就会把右侧值传入到构造函数中,相当于隐式调用构造函数。在编码过程中,可能会不小心,隐式触发调用构造函数,造成错误,这时可以用explicit关键字屏蔽隐式构造函数调用。
#include <iostream>using namespace std;class Value
{
private:int val;
public:Value(int v)//如果加上explicit 关键字:val(v){cout << "构造函数成功" << endl;}int get_val(){return val;}
};int main()
{// 隐式调用构造函数Value v1 = 888;//如果构造函数加上explicit 关键字,隐式调用构造函数会失败cout << "1 " << v1.get_val() << endl;// 隐式调用拷贝构造函数Value v2 = v1;cout << "2 " << v2.get_val() << endl;return 0;
}
4、析构函数
构造函数 | 析构函数 |
不需要用户调用,创建对象时自动调用 | 当对象销毁时,自动调用 |
函数名是类名 | 函数名是 ~类名 |
构造函数可以重载 | 析构函数没有参数,不能重载 |
用于创建对象时,初始化对象属性 | 用于销毁对象时,释放资源 |
有返回值但是不写,void也不行。返回值是新创建的对象 | 没有返回值 |
析构函数:对象销毁前做善后清理工作。在对象销毁时,之前为成员变量开辟的内存空间需要进行释放。
形式:~类名(){ }
析构函数的特点:
1、函数名为~类名,因为没有参数,所以不能重载
2、不显式给出析构函数,会有默认的析构函数,函数体为空。给出析构函数,编译器就不在提供默认析构函数
3、对象销毁时,自动调用
#include <iostream>using namespace std;
class Test{
private:string name;
public://构造函数Test(string n):name(n){}//析构函数~Test(){cout << name << " 的析构函数调用了" << endl;}
};
int main()
{Test t1("admin");{//局部代码块Test t2("lisi");}Test *t3=new Test("zhangsan");delete t3;t3=NULL;cout << "主函数执行完毕" << endl;return 0;
}