在学习了c++类之后,终于可以把我的bug写到博客里面了
这篇博客将会重点记录c++类中的一些细节而不会完整的梳理知识点框架,相信自己写了一遍以后可以加深对这些知识点的理解
拷贝构造函数
拷贝构造函数就是构造函数,然后形参就是该构造函数相同类型的对象
形参用引用,加const
拷贝构造函数的形参应当是引用形式(避免形成形参副本,详见后面),并且前面加上const(也有说法是“其形参必须是引用,但并不限制为const,一般普遍的会加上const限制”)如果没有const的话会出现报错。
同时,const和引用还可以避免引用复制,从而提高程序效率
A(A & c) {}
cannot bind non-const lvalue reference of type 'A&' to an rvalue of type 'A'
主要原因是编译器,由于引用传进来的临时变量最后都会消失,对这个临时变量的修改就没有意义,所以有些编译器要求临时变量不能作为非const引用。这时在前面加上const就没问题了。
A(const A & c) {}
什么时候调用拷贝构造函数
下面这几种情况都会调用
A b(a);
A b = a;
new A(a)
//小a是一个已经存在的A的对象
除此之外,将一个对象作为实参,传递给被调用函数的形参对象时也会调用拷贝构造函数
用例子来说明:
#include <iostream>
using namespace std;
class A {
public:
int v;
A()=default;
A(int v):v(v){}
A(const A& that):v(that.v+2){}
};
void PrintAndDouble(A o)
{
cout << o.v;
cout << endl;
}
int main()
{
A a(5);
A b = a;
PrintAndDouble(b);
A c = 20;
PrintAndDouble(c);
A d;
d = a;
cout << d.v;
return 0;
}
输出为:
9
22
5
上面的代码每调用一次拷贝构造函数都会使v的值增加2,b.v的值为5+2=7,随后调用PrintAndDouble(b)之后b作为实参传递给形参o后又调用一次拷贝构造函数,o.v=7+2=9,所以输出为9
扩展:看注释~
class A {
public:
int v;
A()=default;//无参数构造函数
A(int v):v(v){}//有参数构造函数
A(const A& that):v(that.v){}//拷贝构造函数
};
int main()
{
A a(5);//调用有参数的构造函数
A c = 20;//先调用有参数的构造函数构造一个隐式的对象,并使这个类的v的值为20,
//然后再调用拷贝构造函数把这个隐式的对象拷贝构造给C
A d;//调用无参数的构造函数
d = a;
A e(a)//调用拷贝构造函数
A b = a;//调用拷贝构造函数,注意这里不是赋值运算
return 0;
}
注意上面d已经定义后再执行d = a语句则不会调用拷贝构造函数
浅拷贝和深拷贝
当类中含有指针成员时,我们需要对指针成员进行深拷贝,不然可能会出现同一块内存被释放两次的情况
当一类中含有指针成员时通常应该重写:
1.构造函数为指针分配内存并复制指针指向的对象(深复制)
2.=操作重写,完成深复制策略
3.析构函数中释放内存
如果类中没有指针成员使用系统提供的默认构造函数就可以了
buffer = new char[length + 1];
for(int i = 0; i < length; i++){
buffer[i] = str[i];
}
值得一提的是new后面[ ]中的数字代表长度,所以new char[0]不会创建空间。代码中length+1是为了给'\0'留出空间
一个例题:
class Cat
{
string name;
Cat ** family;
int size = 0;
public:
Cat(string name="") {
this->name = name;
family = new Cat*[0];
size = 0;
}
//其余省略
};
Cat& Cat::operator=(const Cat &cat) {
size = cat.size;
name = cat.name;
delete [] family;
family = new Cat*[1];
/*for(int i = 0; i <= size; i++){
family[i] = cat.family[i];
}*/
return *this;
}
int main(){
int num_of_cats;
cin >> num_of_cats;
Cat * cats = new Cat[num_of_cats];
string name;
for (int i = 0; i < num_of_cats; ++ i) {
cin >> name;
cats[i] = Cat(name); // operator =
}
}
这里运用了重载运算符(operator=),发现如果不注释掉for循环的family[i] = cat.family[i],则会发生内存泄露,我认为原因是期初创建对象时new的是Cat*[0]没有空间,导致cat.family为nullptr,而进行赋值语句的话编译器就会创造空间,最终导致内存泄露
值得注意的是,由于类中的成员有指针,这时构造函数可能会传入一个指针对象,若是char*类型我们可以用strlen()函数来获取长度,string类型用size()来获取长度,注意strlen()函数不包括'\0',所以最后new空间时要+1
Creature::Creature(const char* _sound, int _age){
age = _age;
sound = new char[strlen(_sound) + 1];//此处要+1
for(int i = 0; i < strlen(_sound); i++){
sound[i] = _sound[i];
}
sound[strlen(_sound)] = '\0';
}
另一种表现形式,arr[]中arr同样是char类型的指针
void setjob(char arr[]){
delete[] job;
job = new char[strlen(arr) + 1];
for(int i = 0; i < strlen(arr) + 1; i++){
job[i] = arr[i];
}
}
若指针是一个类的类型时,一般该类中会有一个记录包含的类的数量的成员,如上面cat例题中的size
使用引用避免形成形参副本
c++相比起c多了引用这个东西,因为实参的引用是实参的别名,使用引用时就不会复制一个形参副本,这种性质在类中也可以起到很妙的作用。
一个例题:
class Point{
private:
int x;
int y;
public:
Point() : x(0), y(0) {}
Point(int x, int y) : x(x), y(y) {}
Point(Point &p) : x(p.x), y(p.y) {}
~Point() {}
void move(int m, int n){
x = m;
y = n;
}
int getX() const{
return x;
}
int getY() const{
return y;
}
void setX(int x){
this->x = x;
}
void setY(int y){
this->y = y;
}
};
class AofPoint{
private:
Point *point;
int size;
public:
AofPoint(int number){
size = number;
point = new Point[number];
}
~AofPoint(){
delete [] point;
}
AofPoint(AofPoint & a){
size = a.size;
point = new Point[size];
for(int i = 0; i < size; i++){
point[i].setX(a.visit_point(i + 1).getX());
point[i].setY(a.visit_point(i + 1).getY());
}
}
Point & visit_point(int num){
//返回的是对象别名
return point[num - 1];
}
};
#include "Point.h"
#include "AofPoint.h"
#include <iostream>
using namespace std;
int main()
{
AofPoint afp(3);//创建afp对象时,也设置了动态对象数组的大小
afp.visit_point(1).move(4,6);
afp.visit_point(2).move(8,16);
cout << "point(1):x=" << afp.visit_point(1).getX() << ",y=" << afp.visit_point(1).getY() << endl;
cout << "point(2):x=" << afp.visit_point(2).getX() << ",y=" << afp.visit_point(2).getY() << endl;
AofPoint afp2(afp);
cout << "point(1):x=" << afp2.visit_point(1).getX() << ",y=" << afp2.visit_point(1).getY() << endl;
cout << "point(2):x=" << afp2.visit_point(2).getX() << ",y=" << afp2.visit_point(2).getY() << endl;
return 0;
}
本题中的Point & visit_point(int num)函数就运用到了引用不会复制形参副本的思想,如果写为Point visit_point(int num)的话,前面的afp.visit_point(1).move(4,6)和afp.visit_point(2).move(8,16)这两条语句执行完,x、y的值确实会发生变化,但之后的cout中又使用afp.visit_point(1).getX(),这时编译器为了能返回一个Point对象,就会拷贝构造一个新的对象,这会导致拷贝构造的形参会调用默认构造函数进行初始化,x、y又初始化为0。
而运用引用的话返回的是对象别名,编译器则不会拷贝构造新对象,x、y自然也不会初始化为0了
由于学习不精,文章有不正确的地方望读者指正。