构造函数的特点:自动会被调动,自己不能主动调动构造函数
构造函数是拿一种整型数据来初始化对象,也就是内置类型
拷贝构造函数是拿一个对象去初始化另一个对象空间,存活在1.实参对象和形参对象结合时,2.return 返回一个对象时
Int fun(Int x)
{
int a=x.Value();
Int tmp(a);
return tmp;
}
int main()
{
Int a(10);
Int b;
b=fun(a);
return 0:
}
当它一个实参对象,初始化一个形参对象时,将调动拷贝构造函数,当return tmp; 我们要返回一个临时对象,我们要调动拷贝构造函数,构造一个临时对象,作为返回。因为无法将tmp直接给给b对象,如果直接把tmp给给b对象,将会导致tmp在两个函数内都可以生存,这和我们局部对象只能在函数里有效当函数无效冲突了,在这里,有一个概念:将亡值。返回tmp时,我们将会在主函数的栈帧中构建一个临时对象,当把这个临时对象给b赋值完成之后,这个临时对象的生存期也就到了,我们把这个对象称之为将亡值
析构函数特点:把一个对象,释放它所在的资源
缺省函数:如果你没有写构造函数,系统会给你缺省构造函数(只是创建对象,无法对对象本身进行初始化,数据是随机值),拷贝构造函数(把对象地址抓住,把将要构建的对象的地址抓住,按位进行拷贝)
Int& operator=(const Int& it)
{
}//a=b
缺省构造函数:把b的地址抓住,把a的地址抓住,把b的内容按一个字节一个字节地拷贝给b,按位拷贝
有的时候缺省拷贝构造函数可以满足我们的要求,有的时候不能满足要求,如果没有申请资源,可以满足我们的要求,如果要申请资源,那么就不能满足我们的要求
Int& operator=(const Int& it)
{
if (this != &it)//防止自己给自己赋值,a=a
{
value = it.value;
}
cout << this << " = " << &it << endl;
return *this;
}//a=b
这里的赋值语句返回的是类型的引用,为什么在这里要返回自身类型的引用?a=b; 返回this指针,this指针指向a,*this就是a对象本身,把自己本身以引用的形式返回,当函数死亡,this指针也死亡,this所指之物a对象不受这个函数的影响,当这个函数结束,a对象依然存在,所以可以引用返回。我们运算符的重载,希望重载的运算符的能力接近于它对于内置类型所具有的能力。为了实现a=b=c,连续赋值的能力 。 a=operator=(&b,c); b调动赋值语句,把b的地址传递进去,作为成员函数,含有this,拿c去初始it,it是c的别名,this指向b的地址,返回的 *this,就是b对象,把b对象赋值给a
Int a(a); 编译器编译时会给你报错,因为你a还没构造完,怎么可以给自己构建呢?
void SetValue(int x) { value = x; }
int GetValue() const { return value; }
int& Value() { return value; }
int& Value() { return value; } 既可以取值又可以赋值
int main()
{
Int a(10);
Int x=a.Value();
a.Value()=100;//把100赋值给Value()的返回值,这个返回值Value对象也就是a的别名 被系统解释成a.value=100;
return 0;
}
对于普通对象可以去调用它
如果const Int a(10); 变成常对象,说明a里面属性值不可改变,当调动Value以普通引用返回,可以改变value值,产生矛盾,语法出现二义性。
这种函数一般是成对出现
const int& Value() { return value; }
const Int a(10);
常对象调用常对象方法
普通对象调用普通方法
int funa()
{
int tmp = 10;
return tmp;
}
int main()
{
int x=0;
x=funa();
cout<<x<<endl;
return 0;
}
funa()创建一个整型变量,return tmp,构建一个临时变量作为过渡,给x
当我们程序开始执行时,执行主函数,编译器发现是内置类型,给主函数分配一个栈帧,给了一个x变量,调动funa,在funa分配一个栈帧,定义一个tmp=10;return tmp; 把tmp的值10放在eax,然后funa的return tmp;意味着结束了,分配给的栈帧就结束了,返回到主函数的时候,再通过寄存器eax获取的值给给x
如果引用接收 int &x=funa(); 是有问题的,引用相当于别名,系统编译的时候,把引用编译成一种指针,当我们调动funa的时候,定义了一个tmp=10;return tmp;我们在寄存器eax,放的是tmp的整型值,这个引用,想着去指向eax或者这个10,是不允许的,将亡值的概念(不能取地址)
const int &x=funa();
上面这个方法是可以的,万能引用。虽然说返回的值到达eax
相当于执行
int tmp = eax;
const int &x = tmp;
这里引用的是主函数的一块内存空间
下面这个方法也可以!右值引用将亡值
int&& x = funa();
// int tmp = eax;
// int &&x = tmp;
这个将亡值生存期变得和x一样
下面这段代码非常重要
int& fun()
{
int x = 10;
return x;
}
int main()
{
int a=fun();
int&b=fun();
cout<<a<<endl;//打印出10
cout<<b<<endl;//打印出随机值
return 0;
}
但是这段代码是错误的!
生存期的问题
如果想以引用的方式返回某个值,此数据或者此变量或者此对象的生存期不地受于fun函数的限制,也就是你引用返回时,当fun这个函数结束时,x这个变量的生存期并没有到期,x仍然要活着。要想把某个变量返回它的地址,或返回它的引用时,这个变量的生存期不受此函数的影响
此例子中,返回x,x的生存期受这个fun函数的影响,fun()结束,x的生存期也就结束。当程序运行中,主函数运行,给主函数分配一个栈帧,定义一个变量a,此时a还没有值,调动fun(),给fun分配一个空间,定义x=10;返回x,以引用返回,引用本质被系统解释成地址,eax存放x的地址,return x;结束,分配给fun的栈帧就销毁了,eax扔指向这个空间,虽说这个空间已经失效了,a是整型变量,不可能存放eax,这一步系统解释:a=*eax;这里有一个问题:这个空间受不受侵扰?如果不受侵扰可以读出10,如果受侵扰不能读出10,这里的b是一个引用,实际上b是一个指针,返回的是引用,x的地址,b指向x的地址,打印a时,a已经获取值,打印b时,是以指向去打印,这里的cout本身是输出函数(开辟栈帧,覆盖原本fun的函数的空间),侵扰这一块内存区域,所以打印的是随机值。
单线程赋值时没有被干扰
多线程一定错误赋值有可能被打断
打印的时候,已经把10拷贝给a,侵扰它,a已经是10,b并没有拷贝这个值
Int fun()
{
Int a(10);
return a;
}
int main()
{
Int x=fun();//OK
}
当我们调动fun();产生一个对象a,返回的时候,产生一个临时对象a,函数fun结束,a对象消亡,把临时对象a拷贝构造x
Int &x=fun();//error
错误原因:临时对象a是将亡值,不能引用一个将亡值
当函数结束之后,这个临时对象会被析构掉,语法规则不允许
const Int &x=fun();//ok
常引用,引用它构造的对象 this引用到这个主函数的临时对象,this不能改变它
Int&&x=fun();//ok
右值引用 引用将亡值
系统有缺省的赋值语句
在赋值的时候,系统的默认赋值语句是把a的地址,b的地址抓住,把a的值拷贝进来,打印的是10,但是不对。
因为建立a的时候,调用赋值语句的时候,会把原来fun的空间再一次重新洗牌,a的值被覆盖掉。
打印的仍然是随机值
引用返回的对象已经消亡了
class String
{
char* str;
public:
String(const char* s = nullptr)
{
if (s != nullptr)
{
int len = strlen(s);
str = new char[len + 1];
strcpy_s(str, len + 1, s);
}
else
{
str = new char[1];
*str = '\0';
}
}
~String()
{
if (str != nullptr)
{
delete[]str;
}
str = nullptr;
}
};
系统默认的缺省的拷贝构造函数
String(const String& st)//浅拷贝
{
str = st.str;
}
自己写的深拷贝
String(const String& st)//深拷贝
{
int len = strlen(st.str);
str = new char[len + 1];
strcpy_s(str, len + 1, st.str);
}
当拿s1给s2赋值时,缺省的赋值函数,把s1的str值给s2的str,导致s2的str直接指过来。这样有什么缺陷呢?
1.内存泄漏2.当析构s2对象,程序崩溃,二次释放
String& operator=(const String& st)
{
str = st.str;
return *this;//按位赋值,缺省构造
}
自己写的赋值函数
String& operator=(const String& st)
{
if (this != &st)
{
delete[]str;
int len = strlen(st.str);
str = new char[len + 1];
strcpy_s(str, len + 1, st.str);
}
return *this;
}
主函数分配一个s1(未构建),str随机值,当我们去调用fun(),分配一个栈帧,s,str,从堆区分配一个空间,把yhpinghello拷贝进去指向它,普通的返回,按值返回,构建一个临时对象,调动拷贝构造函数,临时对象str也指向一个堆区空间yhpinghello,临时对象构建完成后,函数要结束,s的生存期到了,调用析构函数释放空间,堆区不存在,返回到主函数时,这个临时对象要去构建s1,开辟空间,把yhpinghello拷贝进来,然后临时对象生存期到了,析构函数释放掉,此方法创建多个对象,效率低
移动构造
系统的移动构造
String(String&& s)
{
str = s.str;
//delete[]s.str;
s.str = nullptr;
}
返回一个s,就是要返回一个将亡值,系统将s初始化给String(String&& s)的s,调动移动构造,把资源移动
把s对象资源转移给s1,把s.str置为空
把局部对象的资源转移给需要它的
这两个写法不一样
第一个写法是拿返回的对象构建s1,调动移动构造,把s的资源给s1
第二个写法不会调动移动构造,构造函数是创建对象,拷贝构造函数也是创建对象,移动构造也是创建对象,但是s2这个对象是已经存在的,调动移动赋值函数
String& operator=(String&& s)
{
if (this != &s)
{
delete[]str;
str = s.str;
s.str = nullptr;
}
return *this;
}
原始方法
如果有移动构造,就方便
等价
return s;拿移动构造构建临时对象,临时对象的str指针指向s对象的yhpinghello并且把s的str置为空,当这个函数构建完成之后,销毁s对象,不会被yphinghello释放,返回主函数,将亡值调动移动赋值语句,这时候s就是移动赋值的别名
这个方法new空间只new一次
将返回值s直接构造s1(移动构建)
移动构造函数,移动赋值函数系统有,但是如果要用,要自己去实现