C++知识点随笔(一):this指针、拷贝构造函数、初始化列表

时间:2021-06-04 19:49:13

一、成员属性与成员函数

成员属性是定义对象才有的,sizeof()计算类的大小的时候包含在内;
而成员函数是编译时就放在代码区的,所以sizeof()并不把其计算在内,并且调用成员函数并不用new出对象的空间,如下面的代码就可以正常运行:

CPeople *p = NULL;
p->Show();

普通成员函数和static成员函数区别:静态成员函数是父类和所有子类共享的使用类名作用域+函数名就可以调用,而普通成员函数虽然不用new对象空间,但是还要定义对象才可以使用)
而成员函数在使用成员属性的时候,过程是这样的:我们定义的对象在调用成员函数的时候,首先将当前类的地址给成员函数,然后成员函数就像使用链表中的结构体一样,根据这个对象的首地址,获得这个对象的成员属性,从而进行相应的操作。
通过上面的介绍我们可以认识到:其实类就是结构体和函数的结合,而this指针就是结合这两者之间的工具。我们的成员属性就相当于结构体中的属性,而类的成员函数在调用对象的成员属性时就相当于函数调用结构体的属性值一样,这样其实我们就可以用C语言分别写结构体和函数来模拟实现类的功能了。

二、拷贝构造函数

默认的拷贝构造函数是浅拷贝,也就是没有申请空间的,所以我们在传递栈区成员属性的时候不会发生问题,因为它只是值传递,可是当我们传递堆区的变量(指针)时,就会发生问题,因为浅拷贝只是复制了指针的地址,所以当一个对象析构的时候这个指针也会随之销毁,而另一个对象在析构时,这个空指针继续delete[]当然会崩溃了。
所以我们的解决办法就是:
1. 重写拷贝构造函数,把它重写为深拷贝,即有指针拷贝的时候,我们一定要new出相应的地址,这样调用拷贝构造函数就不会发生问题了。
2. 传递对象的时候使用引用或者是对象的指针,这样就不会调用拷贝构造函数,从而就不会发生问题了。

class CPeople
{

public:
char* str;
public:
CPeople()
{
str = new char[100];
strcpy(str,"asdadsasd");
}
//CPeople(CPeople& pop) // 拷贝构造函数 浅拷贝
//{
// this->str = pop.str;
// cout << "COPY" << endl;
//}
CPeople(CPeople& pop) // 拷贝构造函数 深拷贝
{
cout << "COPY" << endl;
this->str = new char[strlen(pop.str)+1];
strcpy(this->str,pop.str);
}
~CPeople()
{
delete[] str;
str = NULL;
}
};

//void QQ(CPeople pp)
//{
// pp.Show();
//}

CPeople& QQ(CPeople& pp) //传递引用,就不用使用深拷贝了
{
pp.Show();
return pp; //返回对象的时候同样要注意,如果不是引用或指针同样会调用拷贝构造函数
}

int main()
{
CPeople pep;
QQ(pep);

return 0;
}

为什么C++默认的是浅拷贝而不是深拷贝呢?
因为我们拷贝构造函数所传递的参数只有一个对象的引用,里面的指针成员属性也只是知道他们的首地址,并不知道我们在拷贝构造函数当中应该new多大的空间,如果是string还好,我们可以通过strlen()来获得空间大小,可是如果是一个int *呢?没办法了。所以C++给我们提供的拷贝构造函数只是告诉我们可以这样使用,具体操作的时候还是要程序员来进行修改的。

为什么C++拷贝构造函数要使用引用?
因为拷贝构造函数如果不使用引用的时候,参数是这样的:

CPeople(CPeople pop)

那么每次拷贝的时候,形参都是一个构造的过程,就会产生死循环,而我们知道引用只是把原来的对象起个别名,所以执行一次就结束了。

三、初始化列表

初始化列表作用:给成员变量初始化。
使用方法如下:

class CPeople
{
public:
int m_i;
int m_j;
public:
CPeople(int num):m_j(num),m_i(m_j) //初始化列表
{

}
};

int main()
{
CPeople pp(100);
cout << pp.m_i << " " << pp.m_j << endl;

return 0;
}

因为初始化列表执行的顺序 是根据变量定义的顺序执行的,所以先初始化pp.m_i,其值不确定(因为pp.m_j还没有初始化),然后再初始化pp.m_j,所以它的值为100。
如果我们稍作修改:

int& m_i;

那么结果就都是100了,因为先执行 m_i(m_j) 的时候就相当于给 m_j 起了一个别名,之后再执行 m_j(num) 的时候就相当于给他们一起赋值了(其实只是一个变量起了连个名字而已)。

如果一个类中包含对象成员,并且想要执行带参数的构造函数时,也要在初始化列表中给参数赋值:

class A
{

public:
int num;
A(int a)
{
num = a;
}
};

class CPeople
{

public:
A m_a;
CPeople():m_a(123) //初始化列表
{
cout << m_a.num << endl;
}
};

需要注意的是:
1. 普通成员变量一般在函数体内初始化,而const成员变量因为定义的时候必须初始化,所以const成员变量的初始化必须使用初始化列表。
2. static成员变量的初始化必须在类外,并且static成员函数中不能使用非static的成员变量,因为static成员函数没有this指针。
3. static const是唯一一个可以在类内初始化的成员变量。
具体初始化表格如下:
C++知识点随笔(一):this指针、拷贝构造函数、初始化列表