1.C++空类中成员函数
空类默认有6个默认函数,分别为:
class Empty{
public:
Empty(); // 缺省构造函数
Empty( const Empty& ); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator=( const Empty& ); // 赋值运算符
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 取址运算符 const
};
C++的这些默认函数,只有类在实例化的时候才会被创建,接下来我们分别介绍一下这六个成员函数
1.1 缺省构造函数
-
是一种特殊的类成员函数,当创建一个类对象时,调用构造函数对类的数据成员进行初始化和分配内存;
-
构造函数的命名需和类名完全相同;
-
构造函数可以被重载,可以多个,可以带参数;跟析构函数不同,析构函数只能有一个,不能被重载,不带参数;
1.2 缺省拷贝构造函数
- 函数名和类型名一样,有两种原型:
- 参数为地址参数,为了防止无限构造,行成死循环;
- const目的是常引用,不能改变里面的值;
Empty(Empty &e);
Empty(const Empty &e); // 这种规定在创建新对象的时候不得修改被拷贝的对象
- 拷贝构造函数的参数必须是对象的引用,否则编译不过,然而这是为什么呢?
// 如果不是引用,而是通过传值的方式将实参传递给形参,这中间本身就要经历一次对象的拷贝过程,而对象拷贝则必须调用拷贝构造函数,如此一来则会形成一个死循环,无解。所以拷贝构造函数的参数必须是对象的引用。
- 拷贝构造函数除了能用对象引用这样的参数,还能有其他参数,但这个参数必须给出默认值,如下:
Empty(const Empty &e, int a = 5);
- 如果我们不声明一个拷贝构造函数,则会自动为类生成一个拷贝构造函数,它的功能简单,只能将原对象的所有成员变量复制给当前创建的对象,那么我们什么时候才能用到这个拷贝构造函数呢?
#include<iostream>
using namespace std;
class Array
{
public:
Array(){length = 0;num = NULL;}
Array(int *A,int n);
void setnum(int vallue,int index);
int *getaddress();
int getaddress();
void display();
private:
int length;
int *num;
};
Array::Array(int *A,int n)
{
num = new int [n];
length = n;
for (int i = 0;i < n; i++)
num[i] = A[i];
}
void Array::setnum(int value,int index)
{
if(index < length)
num[index] = value;
else
cout<<"index out of range!"<<endl;
}
void Array::display()
{
for(int i = 0;i < length;i++)
cout<<num[i]<<" ";
cout<<endl;
}
int *Arry::getaddress()
{
return num;
}
int main()
{
int A[5] = {1,2,3,4,5};
Array arr1(A,5);
arr1.display();
Array arr2(arr1);
arr2.display();
arr2.setnum(8,2);
arr2.display();
arr1.display();
cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;
return 0;
}
运行结果如下:
1 2 3 4 5
1 2 3 4 5
1 2 8 4 5
1 2 8 4 5
00331F58 00331F58
/*
在本例中,我们重新定义了一个Array类,可以理解为一个整形数组类,这个类中我们定义了两个成员变量:整形指针num和数组长度length。
类中定义了一个默认构造函数,声明了一个带参构造函数。默认构造函数很简单,带参构造函数则是用于将一个已有的数组全部拷贝给类对象。
除了两个构造函数之外,我们还定义四个成员函数,一个是用于修改数组中数值的setnum函数、一个打印数组中所有元素的display函数、一个返回数组首地址的函数getaddress和一个返回数组长度的函数getlength。除了默认构造函数之外和getlength函数之外,所有的函数在类外都有定义。
接下来我们看一下主函数。主函数中,我们先定义了一个数组,包含五个元素,分别是从1到5。之后用Array类创建对象arr1,并且用A数组初始化对象arr1,此时arr1对象相当于拥有一个数组,该数组包含5个元素,打印出来的结果是“1 2 3 4 5 ”,没有问题。之后用arr1对象初始化arr2对象,因为我们在类中没有显示地定义一个拷贝构造函数,因此系统会自动为我们生成一个拷贝构造函数,该拷贝构造函数的定义如下:
Array::Array(Array &a)
{
length = a.length;
num = a.num;
}
通过系统自动生成的拷贝构造函数完成arr2对象的创建,同样的arr2也是有5个元素的数组,打印出来的结果是“1 2 3 4 5 ”,同样没有问题。
之后我们调用成员函数setnum,将arr2对象下标为2的元素修改为8(原先是3)。此时打印arr2中数组元素,结果为“1 2 8 4 5 ”,正确,arr2第三个元素确实被修改掉了。
后我们再调用arr1.display(),奇怪的事情发生了,它的打印结果竟然也是“1 2 8 4 5 ”!我们之前并未修改过第三个元素的值的,这是怎么一回事呢?不急,我们再来看一下最后一句“cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;”其显示结果竟然是一样的!看到这里是不是有些明白了上面的问题呢?很明显,arr1和arr2所指向的数组是同一个数组,在内存中的位置是一致的,因此当我们利用对象arr2去修改数组中第三个元素的数值的时候,arr1中的数组也被修改了,其实它们本来就是使用的是同一个内存中的数组而已.
*/
- 拷贝构造函数参数为引用,系统自动生成的拷贝构造函数功能简单,只是将arr1的数组首地址直接赋值给arr2的数组首地址,也即num = a.num;这必然导致两个对象指向同一块内存。既然问题出在系统自动生成的拷贝构造函数上,自然要从拷贝构造函数上下手了。下面我们将正确的程序展示
#include<iostream>
using namespace std;
class Array
{
public:
Array(){length = 0;num = NULL;}
Array(int *A,int n);
Array(Array &a);
void setnum(int value,int index);
int *getaddress();
void display();
int getlength(){return length;}
private:
int length;
int *num;
};
Array::Array(Array&a)
{
if(a.num!=NULL)
{
length = a.length;
num = new int [length];
for(int i = 0;i < length;i++)
num[i] = a.num[i];
}
else
{
length = 0;
num = 0;
}
}
Array::Array(int *A,int n)
{
num = new int [n];
length = n;
for(int i = 0;i < n;i++)
num[i] = A[i];
}
void Array::setnum(int value,int index)
{
if(index <length)
num[index] = value;
else
cout<<"index out of range!"<<endl;
}
void Array::display()
{
for (int i = 0;i < length;i++)
cout<<num[i]<<" ";
cout<<endl;
}
int *Array::getaddress()
{
return num;
}
int main()
{
int A[5] = {1,2,3,4,5};
Array arr1(A,5);
arr1.display();
Arry arr2(arr1);
arr2.display();
arr2.setnum(8,2);
arr2.display();
arr1.display();
cout<<arr1.getaddress();" "<<arr2.getaddress()<<endl;
return 0;
}
运行结果如下:
1 2 3 4 5
1 2 3 4 5
1 2 8 4 5
1 2 3 4 5
00311F58 00487268
通常,如果一个类中包含指向动态分配存储空间的指针类型的成员变量时,就应该为这个类设计一个拷贝构造函数,除了需要设计一个拷贝构造函数之外,还需要为它添加一个赋值操作符重载函数。
- 如果不想让对象发生拷贝行为,可以声明一个拷贝构造函数,并将其设置为private属性。
1.3 缺省析构函数
析构函数与构造函数相对应,是对象销毁时自动调用的,主要特点如下:
- 析构函数只能有一个,不能重载;
- 析构函数不能有参数;
- 在主函数中,析构函数在return语句之前执行;
1.4 缺省赋值运算符
-
拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,赋值运算符是对于一个已经初始化的对象来进行赋值操作。
A a; A b=a; //调用拷贝构造函数(b不存在) A c(a) ; //调用拷贝构造函数 /****/ class A; A a; A b; b = a ; //调用赋值函数(b存在)
-
数据成员包含指针对象时,需考虑两种不同的处理需求:一种是复制指针对象,另一种是引用指针对象。拷贝构造函数大多数情况下是复制,而赋值函数是引用对象
-
实现不一样。拷贝构造函数首先是一个构造函数,它调用时候是通过参数的对象初始化产生一个对象。赋值函数是把一个新的对象赋值给一个原有的对象,所以如果原来的对象中有内存分配先要把内存释放掉,而且要检查一下两个对象是否为同一个对象,如果是不做任何操作,直接返回。
-
如果不想写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的缺省函数,最简单的办法是将拷贝构造函数和赋值函数声明为私有函数。
-
对象不存在,没有用别的对象初始化,就是调用了构造函数;
-
对象不存在,用别的对象初始化,就是用了拷贝构造函数;
-
对象存在,用别的对象来给它赋值,就是赋值函数;