拷贝构造函数(按位拷贝)
拷贝构造函数是构造函数的一种
Complex(Complex &cx) //形参必须是引用
如果拷贝构造函数形参不是引用的情况:(会发生无穷递归)
下列代码可以展示:
Complex(Complex cx)
{
}
int main()
{
Complex c1;
fun(c1);
Complex c2 (c1);
所以,拷贝构造函数形参不允许设计成值类型。
Complex( Complex &cx):Real(cx.Real),Image(cx,Image)//不加const编译无法通过(const Complex &cx)
{
cout<<"copy Create Complex:"<<this<<endl;
}
int main()
{
const Complex c1(1,2);
战略意义;
class CDate // 日期类型
{
int year;
int month;
int day;
public:
CDate(int Year, int Month, int Day)
{
year = Year;
month = Month;
day = Day;
}
CDate(CDate& dt) :year(dt.year), day(dt.day), month(dt.month)
{
//year = dt1.year;
memmove(this, &dt, sizeof(CDate));//禁止使用
}
void print()const
{
cout << year << endl;
}
};
//类外定义
void funa(CGoods ca){}
void funb(CGoods &cb){}
void func(const CGoods &cc){}
void fund(CGoods *pc){}
void fune(const CGoods *pc){}
int main()
{
CDate dt1(2022, 1, 1);
funa();
}
在这里插入代码片
构造函数的作用主要三种:
1.类型的转换
2.对象的构造
3.初始化对象
类型的转换:
Int a(10);
int x=100;
a=x;
a=(Int)x;//构造函数中加explicit关键字,必须显示转换。 主程序必须把x强转。
a.print();
只有单参的构造 函数才有类型转换的能力(构造函数参数可以加缺醒值 剩一个参数也可以)。
public:
//explicit Int(int x,int y=0):value(x) //显示转换,单参是x.
explicit Int(int x,int y=0):value(x)
{
cout<<"Create Int :"<<this<<endl;
}
int main()
{
a=(Int)(x,y); //只取右边y的值给构造函数参数x.
a=Int(x,y);//构造函数
}
对象赋值给内置类型
强转运算符的重载
operator int()const {return value;}//加const的目的
//operator int &() {return value;}
//实际返回的是强转后的类型
int main()
{
x=(int)a;
x=a.operator int ();
//x=operator int(&a);
return 0;
//(int&)a=100;
}
构造函数的返回类型是对象。
析构函数的返回类型是对象的地址。
下面代码演示对象不能比较的原因:
class Student
{
public:
char s_id[10];
cha s_namep[10];
char s_age;
};
int main()
{
Student s1;
Student s2;
s1>s2;//比较的意义在哪?
}
s1>s2;//比较的意义在哪?
对象的属性之间的比较才是有意义的。
bool operator>(const Int &it )const
{
return this->it.value;
}
bool operator<=(const Int &it )const
{
return !(*this>it);
}
***************************
bool operator==(const Int &it )const
{
return this->value==it.value;
}
bool operator==(const Int &it )const
{
return !(*this==it);
}
我们可以看见 ,要是写比较函数就显得非常繁琐。怎么可以简洁一点呢?
operator int()const {return value;}
int main()
{
Int a(10),b(20);
if(a>b)
{
}
return 0;
}
由以上可以看出通过强转函数 取出a b的值 来比较。
//operator int()const {return value;}
Int operator++ int()const//还得改
{
value++;
}
Int operator++int ()//返回类型为Int &不可以
{
Int tmp=*this;
++*this//this->value+=1;
return tmp;
//return Int(value++);
}
int main ()
{
Int a(10),b(20);
a=++b;
a=b++;
int x=10,y=20;
}
++y=100;
y++=100;//error :1.将y取出到一个临时量,2,将100给这个临时量(右值),因为右值不能被赋值所以不可行。
Int & operator++
{
value+=1;
return *this;
}
Int operator++(int)
{
Int tmp=*this;//++*this;//!x(从右向左结合)
this->value+=1;
reutrn tmp;
//return Int(value++);
int main()
{
a=++b;
//a=b.operator++();
a=b++;
//a=b.operator++(0); // Int operator++(int) 所以用int区分
}
+、+=、的重载举例
class MyString
{
char* str;
public:
MyString(const char* p = nullptr) :str(nullptr)
{
if (p != nullptr)
{
int n = strlen(p) + 1;
str = new char[n];
strcpy_s(str, n, p);
}
else
{
str = new char[1];
str[0] = '\0';
}
cout << "Create MyString" << this << endl;
}
~MyString()
{
delete[]str;
str = nullptr;
cout << "Destroy MyString: " << this << endl;
}
MyString(const MyString& s)
{
int n = strlen(s.str) + 1;
str = new char[n];
strcpy_s(str, n, s.str);
cout << "Copy Create MyString: " << this << endl;
}
void Print() const
{
cout << str << endl;
}
MyString& operator=( MyString& s)//用移动赋值去做
{
if (this != &s)
{
delete[]str;
int n = strlen(s.str) + 1;
str = new char[n];
strcpy_s(str, n, s.str);
}
cout << this << " operator=(): " << &s << endl;
return *this;
} // s1 = s2; // s1.operator=(s2);
//operator=(&s1,s2);
}
};
MyString fun()
{
MyString tmp("h");
return tmp;
}
int main()
{
//MyString s1("nihao");
//s1.Print();
MyString s2("hello");
//MyString s3 =s1+s2;
//MyString s4= "helloworld" + s1;
//MyString s5 = s1 + "hello";//s3.operator=()
MyString s3 = "nihao ";
//s1 = fun();
s3 += s2;//s3=s3+s2;
s3.Print();
return 0;
}
加法运算符重载之对象和变量
MyString operator +(const char *s)const
{
MyString tmp;
int n = strlen(str) + 1 + strlen(s);
char* p = new char[n];
strcpy_s(p, strlen(str) + 1, str);
strcat_s(p, n, s);
tmp.str = p;
p = nullptr;
//return MyString(p);
return tmp;
}
int main()
{
MyString s1("nihao");
MyString s5 = s1 + "hello";//s3.operator=()
s5.Print();
return 0;
加法运算符重载之对象和对象
MyString operator +(const MyString& s2)const
{
MyString tmp;
if (this != &s2)
{
int n = strlen(str) + 1 + strlen(s2.str);
char* p = new char[n];
strcpy_s(p, strlen(str)+1, str);
strcat_s(p,n,s2.str);
tmp.str= p;
p = nullptr;
//return MyString(p);
return tmp;
}
}
int main()
{
MyString s1("nihao");
MyString s2("hello");
MyString s3 =s1+s2;
s3.Print();
return 0;
加法运算符重载之变量和对象
friend MyString operator +(const char* s, const MyString & s2);//类内用友元函数进行函数的声明。
};//
MyString operator +(const char* s,const MyString & s2)//类外定义
{
int n = strlen(s) + 1 + strlen(s2.str);
char* p = new char[n];
strcpy_s(p, strlen(s) + 1, s);
strcat_s(p, n, s2.str);
return MyString(p);
}
int main()
{
MyString s1("nihao");
MyString s4= "helloworld" + s1;
s4.Print();
return 0;
基础做法:
MyString operator +(const char* s,const MyString & s2)//类外定义
{
Mystring tmp;
int n = strlen(s) + 1 + strlen(s2.str);
char* p = new char[n];
tmp.str=p;
p=nullptr;
strcpy_s(p, strlen(s) + 1, s);
strcat_s(p, n, s2.str);
return tmp;
}
在此变量和对象相加的重载函数中我们可以看到的是,我们在函数返回时定义的是右值概念的无名对象 MyString( p ),在整个执行过程中就可以省略在主函数用拷贝构造函数创建临时的对象,无疑是更好地做法,但可能会面临一些其他的问题,比如内存泄漏。
![]()
变量和对象相加的重载函数在类里面定义就涉及到三个参数,一个是对象的this指针,字符串的地址。而+号是二目运算,所以不能放在类里定义。
除了这些 我们还可以看到,在变量和对象相加过程里,在加号运算符重载时的p是没有被释放的。这个接下来要解决。
+=运算符的重载
MyString & operator +=( const MyString& s2)
{
if (nullptr == &s2) { exit(1) ; }
int n = strlen(this->str) + 1 + strlen(s2.str)+1;
char* ip = (char*)realloc(str, 3*sizeof(char)*n);
assert(ip != nullptr);
str = ip;
ip = nullptr;
strcat_s(str, n, s2.str);
return *this;
}
int main()
{
MyString s1("nihao");
MyString s2("hello");
s2+=s1;
}
仿函数
函数调用的重载
有些时候,我们在写代码时会发现,某些功能实现的代码会不断的在不同的成员函数中用到,可又不好将这些代码独立出来成为类的一个成员函数,但又很想复用这些代码。写一个公共的函数是一个解决方法,不过函数用到的一些变量,就可能成为公共的全局变量。而且为了复用这么一片代码,就要单立出一个函数,也不好维护,这时就可以用仿函数了。写一个简单类,除了那些维护一个类的成员函数外,就只是实现一个operator(),在类实例化时,就将要用的,非参数的元素传入类中。这样就免去了对一些公共变量全局化的维护。同时,又可以使那些代码独立出来,以便下次复用。而且,这些仿函数还可以用关联、聚合、依赖的类之间的关系,与用到他们的类组合在一起,这样有利于资源的管理(这点可能是它相对于函数最显著的优点了)。如果再配合上模板技术和policy编程思想,就更是威力无穷了,大家可以慢慢的体会。
class Empty
{
private:
int value1;
int value2;
public:
Empty() {}
Empty(int x,int y) :value1(0) { value1 = x; value2 = y; }
~Empty() {}
Empty& operator+(const Empty& x)
{
Empty tmp (x.value1 + value1, x.value2 + value2);
return tmp;
}
Empty& operator =(const Empty& cx)
{
if (&cx != this)
{
value1 = cx.value1;
value2 = cx.value2;
}
return *this;
}
void print()const
{
cout <<"value1= "<< value1 <<"value2= "<< value2 << endl;
}
Empty operator ()(const int x,const int y)
{
Empty tmp;
tmp.value1 = x;
tmp.value2 = y;
return tmp;
}
Empty operator ()(const int x,const Empty & s)
{
Empty tmp;
tmp.value1 = x;
tmp.value2 = s.value1;
return tmp;
}
};
Empty fun(int x, int y)
{
Empty tmp(x,y);
return tmp;
}
int main()
{
Empty a(2,2);
Empty c(0,0);
Empty d(10,20);
c = a(1,d);//仿函数
c.print();
return 0;
}
学习一个小知识:突破const限制之易变关键字 mutable
//为什么用函数调用的重载而不是函数指针呢?
移动构造 移动赋值
问题: 我们之前提到了堆区开辟的问题。那么,
在加法的运算符重载函数中return tmp ;好还是return MyString ( p ); 比较好?
#if 1
class MyString
{
char* str;
public:
MyString(const char* p = nullptr) :str(nullptr)
{
if (p != nullptr)
{
int n = strlen(p) + 1;
str = new char[n];
strcpy_s(str, n, p);
}
cout << "Create MyString" << this << endl;
}
~MyString()
{
delete[]str;
str = nullptr;
cout << "Destroy MyString: " << this << endl;
}
MyString(const MyString& s)
{
int n = strlen(s.str) + 1;
str = new char[n];
strcpy_s(str, n, s.str);
cout << "Copy Create MyString: " << this << endl;
}
MyString& operator=(MyString& s)
{
if (this != &s)
{
delete[]str;
int n = strlen(s.str) + 1;
str = new char[n];
strcpy_s(str, n, s.str);
}
cout << this << " operator=(): " << &s << endl;
return *this;
} // s1 = s2; // s1.operator=(s2);
void Print() const
{
cout << str << endl;
}
MyString& operator=(MyString&& s)//用移动赋值去做
{
if (&s != this)
{
delete[]str;
str = s.str;
s.str = nullptr;
}
cout << this << "Move operator=(): " << &s << endl;
return *this;
} // s1 = s2; // s1.operator=(s2);
//operator=(&s1,s2);
MyString operator +(const MyString& s2)const
{
if (this != &s2)
{
int n = strlen(str) + 1 + strlen(s2.str);
char* p = new char[n];
strcpy_s(p, strlen(str) + 1, str);
strcat_s(p, n, s2.str);
return MyString(p);
}
}
MyString operator +(const char* s)const
{
int n = strlen(str) + 1 + strlen(s);
//int n = 200;
char* p = new char[n];
strcpy_s(p, strlen(str) + 1, str);
strcat_s(p, n, s);
return MyString(p);
}
MyString& operator +=(const MyString& s2)
{
if (nullptr == &s2) { exit(1); }
int n = strlen(this->str) + 1 + strlen(s2.str) + 1;
char* ip = (char*)realloc(str, 3 * sizeof(char) * n);
assert(ip != nullptr);
str = ip;
ip = nullptr;
strcat_s(str, n, s2.str);
return *this;
}
friend MyString operator +(const char* s, const MyString& s2);
MyString( MyString&& s) noexcept
{
str = s.str;
s.str = nullptr;
cout << "Move Create MyString: " << this << endl;
}
};
MyString operator +(const char* s, const MyString& s2)
{
int n = strlen(s) + 1 + strlen(s2.str);
char* p = new char[n];
strcpy_s(p, strlen(s) + 1, s);
strcat_s(p, n, s2.str);
return MyString(p);
}
//MyString fun()
//{/
// MyString tmp("h");
//return tmp;
//}
int main()
{
MyString s1("hello");
//s1.Print();
MyString s2("@");
s2 = s1+s2 ;
s2.Print();
//s1.Print();
return 0;
}
结果只用到了赋值的重载
。我们将MyString operator +(const MyString& s2)const 的return MyString (p) 换成MyString tmp ;看看区别:
MyString operator +(const MyString& s2)const
{
MyString tmp;
if (this != &s2)
{
int n = strlen(str) + 1 + strlen(s2.str);
char* p = new char[n];
strcpy_s(p, strlen(str) + 1, str);
strcat_s(p, n, s2.str);
tmp.str = p;
p = nullptr;
return tmp;
}
}
我们可以看到,这两者的区别是:返回的是无名对象的加法的重载函数,此函数返回值是右值,不存在从函数栈到主函数栈在开辟空间存变量的一个过程(右值在制度数据区?)所以不会再调动移动构造或者拷贝构造。加法重载结束后的返回右值直接给右值赋值重载运算函数作为形参传入。
而返回的是有名对象的加法重载函数,返回的左值对象需要从函数栈到柱函数栈再到作为移动赋值重载运算符的形参这么一个过程。在函数栈到主函数栈的过程中会隐式的调用拷贝构造或者移动构造,具体看显示的给了哪个构造。
共同点是,没有了移动赋值 运算重载函数,string s1= s1+s2 是无法实现的。