12.1.2 特殊成员函数
C++自动提供了下面的这些成员函数:
1、默认构造函数,如果没有定义构造函数
带参数的构造函数也可以是默认构造函数,只要所有参数都有默认构造函数(只能有一个默认 构造函数,也就是说不能有无参数默认构造函数与 有默认参数值的构造函数同时存在)
2、默认析构函数,如果没有定义
3、复制构造函数,如果没有定义
4、赋值运算符,如果没有定义、
5、地址运算符,如果没有定义
还有移动构造函数,移动赋值运算符
以下实现一个String类
说明这些函数的使用
sting.h
#ifndef STRING_H
#define STRING_H
#include<iostream>
using std::ostream;
using std::istream;
class String
{
private:
char *str;//指向string 的指针
int len;//string 的长度
static int num_strings;//对象的个数
static const int CINLIM =80;//cin 输入的极限
public:
//构造函数和其他成员函数
String();//默认构造函数
String(const char *s);//构造函数
String(const String &);//复制构造函数
//何时调用复制构造函数
/*//每当程序生成了副本,编译器都将使用复制构造函数
* 1、String ditto(motto);
* 2、String metoo =motto;
* 3、String also=String (motto);
* 4、String *p=new String(motto);
*/
~String();//析构函数
int length() const {return len;}
//重载操作符
String & operator=(const String &);//赋值运算符
/*
* 何时使用赋值运算符
* 将已有的对象赋值给另一个对象时,将使用重载的赋值运算符:
* 例如:
* String head("hey man");
* String boy;
* boy=head;
*/
String & operator=(const char *);
char &operator[](int i);
const char &operator[] (int i) const;
//重载操作符 友元
friend bool operator<(const String &st1,const String &st2);
friend bool operator>(const String &st1,const String &st2);
friend bool operator==(const String &st,const String &st2);
friend ostream & operator<<(ostream & os,const String &st);
friend istream & operator>>(istream & is, String &st);
//静态方法
static int HowMany();
};
#endif // STRING_H
sting.cpp
#include "string.h"
#include<cstring>
using std::cin;
using std::cout;
//初始化静态类成员
int String::num_strings=0;
String::String()//默认构造函数
{
len=4;
str=new char[1];
str[0]='\0';//默认string
num_strings++;
}
//类方法 从 C string 构造 String
String::String(const char *s)
{
len =std::strlen(s);//设置大小
str=new char[len+1];//申请内存
std::strcpy(str,s);//初始化指针
num_strings++;//设置对象计数
}
String::String(const String &st)
{
num_strings++;//设置对象计数
len=st.len;
str= new char[len+1];//以下两行属于深拷贝,防止this对象和st对象的str成员指向同一个地址空间
std::strcpy(str,st.str);//复制字符串到新的地址
}
String::~String()//必要的析构函数
{
--num_strings;
delete [] str;
}
//重载操作符 方法
//赋值一个 String 到一个String
String &String::operator=(const String &st)
{
if(this==&st)//避免给自身赋值
return *this;
delete [] str;//释放以前分配的数据
len =st.len;
str=new char[len+1];
std::strcpy(str,st.str);
return *this;//函数返回一个指向对象的引用
}
//赋值一个C String 到 String
String &String::operator=(const char *s)
{
delete [] str;//释放以前分配的数据
len=std::strlen(s);
str=new char[len+1];
std::strcpy(str,s);
return *this;
}
//通过非常量String 读写char
char &String::operator[](int i)
{
return str[i];
}
//通过常量String 只读char
const char &String::operator[](int i) const
{
return str[i];
}
istream &operator>>(std::istream &is, String &st)
{
char temp[String::CIMLIM];
is.get(temp,String::CINLIM);
if(is)
st=temp;
while(is&&is.get()!='\n')
continue;
return is;
}
//重载操作符友元
bool operator<(const String &st1, const String &st2)
{
return (std::strcmp(st1.str,str2.str)<0);//strcmp:依照字母排序,如果第一个参数位于第二个参数之前,
//返回负数;相同,返回0;第一个位于第二个之后,返回正值
}
bool operator>(const String &st1, const String &st2)
{
return st2 <st1 ;//内置的< 、>比较运算符替代strcmp函数
}
bool operator==(const String &st1, const String &st2)
{
return (std::strcmp(st1.str,str2.str)==0);
}
//简单的String 输出
ostream &operator<<(std::ostream &os, const String &st)
{
os<<st.str;
return os;
}
//静态方法
int String::HowMany()
{
return num_strings;
}
12.4.1 返回指向const对象的引用
例如:
Vector f1(50,60);
Vector f2(10,70);
Vector max;
max=Max(f1,f2);
//下面两种实现方式都是可行的
1、
Vector Max(const Vector &v1,const Vector &v2){
if(v1.val()>v2.val())
return v1;
else
return v2;
}
2、
const Vector &Max(const Vector &v1,const Vector &v2){
if(v1.val()>v2.val())
return v1;
else
return v2;
}
对上面的代码进行说明,
1、返回对象将调用复制构造函数,而返回引用不会。因此第二版本做的工作更少,效率更高。
2、引用指向的对象应该在调用函数调用执行时存在。
3、v1和v2都被声明为const引用,因此返回类型必须为const,这样才匹配
12.4.2 返回指向非const对象的引用
两种常用的返回非const对象情形是,重载赋值运算符以及重载与cout一起使用的<<运算符。前者这样做旨在提高效率,而后者必须这样做。
1、operator=()的返回值用于连续赋值:
String s1("Good stuff");
String s2,s3;
s3=s2=s1;
12.4.3 返回对象
如果被返回的对象是被调用函数中的局部变量,则不应该按引用方式返回它,因为在被调用函数执行完毕时,局部对象将调用其析构函数。
例如:
Vector f1(50,60);
Vector f2(10,70);
Vector net;
net=f1+f2;
Vector Vector::operator +(const Vector &b) const
{
return Vector(x+b.x,y+b.y);
}
12.4.4 返回const对象
返回对象,允许以下的操作;
net=f1+f2;
也允许以下操作
f1+f2=net;
f1+f2结果为一个临时变量,如果执行f1+f2=net;,net的内容将会覆盖它们的结果。
如果想要写
if(f1+f2==net)代码的时候,写成了if(f1+f2=net)。这可能导致错误。因此返回const对象可以避免这种错误的出现。
12.5 使用指向对象的指针
在下述情况,析构函数将会被调用
1、如果对象是动态变量(临时对象),则执行完定义该对象的程序块时,将调用该对象的析构函数。
2、如果对象是静态变量(外部、静态、静态外部或者来自名称空间),则在程序结束时将调用对象的析构函数。
3、如果对象是用New创建的,则仅在您显式使用delete删除对象时,析构函数 才会被调用
图 1. 指针和对象
图 2 使用new创建对象
12.5.3再谈定位new对象
定位new运算符让你能够在分配内存时能够指定内存位置。
下面例子使用了定位new运算符和常规new运算符给对象分配内存,其中定义的类和构造函数和析构函数都会显示一些信息,让用户能够了解对象的历史。
#include <iostream>
#include <string>
#include <new>
using namespace std;
const int BUF =512;
class JustTesting{
private:
string words;
int number;
public:
JustTesting(const string &s ="JustTesting",int n=0)
{words=s;number=n;cout<<words<<" constructed\n";}
~JustTesting(){cout<<words<<" destroyed\n";}
void Show() const {cout<< words<<" , "<<number<<endl;}
};
int main()
{
char * buffer =new char[BUF];//获得一个内存块
JustTesting *pc1,*pc2;
pc1=new (buffer)JustTesting;// 在buffer(缓存区)创建对象
pc2 =new JustTesting("Heap1",20); //在堆创建对象
cout<< "Memory block addresses:\n"<<"buffer:"
<<(void*)buffer <<" heap:"<<pc2<<endl;
cout<<"Memory contents:\n";
cout<<pc1<<":";
pc1->Show();
cout<<pc2<<":";
pc2->Show();
JustTesting *pc3,*pc4;
pc3=new (buffer)JustTesting("Bad Idea",6);
pc4 =new JustTesting("Heap2",10);
cout<<"Memory contents:\n";
cout<<pc3<<":";
pc3->Show();
cout<<pc4<<":";
pc4->Show();
delete pc2;//释放Heap1
delete pc4;//释放Heap2
delete [] buffer;//释放 buffer
cout << "Done\n";
return 0;
}
该程序使用New运算符创建了一个512字节的内存缓冲区,然后使用new运算符在堆中创建两个JustTesting,最后,它使用delete来释放使用new分配的内存。下面是程序的输出:
JustTesting constructed
Heap1 constructed
Memory block addresses:
buffer:000001D80C892940 heap:000001D80C891BF0
Memory contents:
000001D80C892940:JustTesting , 0
000001D80C891BF0:Heap1 , 20
Bad Idea constructed
Heap2 constructed
Memory contents:
000001D80C892940:Bad Idea , 6
000001D80C892BB0:Heap2 , 10
Heap1 destroyed
Heap2 destroyed
Done
和往常一样,内存地址的格式和值将随系统而异。
例子在使用定位new运算符时存在两个问题。首先,创建第二个对象时,定位new运算符使用一个新的对象来覆盖第一个对象的内存单元。显然,如果类动态地为其成员分配内存,这将引发问题。
其次,将delete用于pc2和pc4时,将自动调用pc2和pc4指向的对象调用析构函数,然而,将delete[]用于buffer时,不会为使用定位new运算符创建的对象调用析构函数。
因此:程序员必须负责管用定位New运算符用从中使用的缓冲区内存单元。要使用不同的内存单元,程序员需要提供两个位于缓冲区的不同地址,并确保这两个内存单元不重叠。
pc1=new (buffer)JustTesting;// 在buffer(缓存区)创建对象
pc3=new (buffer+sizeof(JustTesting))JustTesting("Better Idea",6);
其中,指针pc3相对于pc1的偏移量为JustTesting对象的大小。
第二个教训是,如果使用定位new运算符来为对象分配内存,必须确保其析构函数被调用。但如何确保呢?对于在堆中创建的对象,可以这样做:
delete pc2;//删除pc2对象
但不可以如下方式:
delete pc1;//删除pc1对象 不可以
delete pc3;//删除pc3对象 不可以
原因在于delete可以与常规New运算符配合使用,但是不可以与定位new运算符配合使用。。例如,指针pc3没有收到new运算符返回的地址,因此delete pc3将导致运行阶段错误。在另外一方面,指针pc1指向的地址与buffer相同,但buffer是使用new[] 初始化的,因此必须使用delete[] 而不是delete来释放。即使buffer是使用new 而不是new[]初始化的,delete pc1也将释放buffer,而不是pc1。这是因为New/delete系统知道已分配的512字节块buffer,但对定位New运算符对该内存块做了何种处理一无所知。
该程序确实释放了buffer:
delete [] buffer;//释放 buffer
正如上述注释指出的,delete[] buffer;释放使用常规new运算符分配的整个内存块,但它没有为定位new运算符在该内存块中创建的对象调用析构函数。因为程序中使用了一个显示信息的析构函数,该析构函数宣布了“Heap1”和“Heap2”的死亡,但却没有宣布”JustTesting“和"Bad Idea “的死亡。
这种问题的解决方案是,显式地为使用定位new运算符创建的对象调用析构函数。正常情况下将自动调用析构函数,这是需要显式地调用析构函数的少数几种情形之一。显示地调用析构函数时,必须指定要销毁的对象。由于有指向对象的指针,因此可以使用这些指针:
pc3->~JustTesting();//销毁指向pc3的对象
pc1->~JustTesting();
下面例子对定位new运算符使用的内存单元进行管理,加入到合适的delete和显式析构函数调用,从而修复了上面例子的问题,需要注意一点是正确的删除顺序,对于使用定位new运算符创建的对象。应该以与创建顺序相反的顺序进行删除。原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区。
#include <iostream>
#include <string>
#include <new>
using namespace std;
const int BUF =512;
class JustTesting{
private:
string words;
int number;
public:
JustTesting(const string &s ="JustTesting",int n=0)
{words=s;number=n;cout<<words<<" constructed\n";}
~JustTesting(){cout<<words<<" destroyed\n";}
void Show() const {cout<< words<<" , "<<number<<endl;}
};
int main()
{
char * buffer =new char[BUF];//获得一个内存块
JustTesting *pc1,*pc2;
pc1=new (buffer)JustTesting;// 在buffer(缓存区)创建对象
pc2 =new JustTesting("Heap1",20); //在堆创建对象
cout<< "Memory block addresses:\n"<<"buffer:"
<<(void*)buffer <<" heap:"<<pc2<<endl;
cout<<"Memory contents:\n";
cout<<pc1<<":";
pc1->Show();
cout<<pc2<<":";
pc2->Show();
JustTesting *pc3,*pc4;
pc3=new (buffer+sizeof(JustTesting))JustTesting("Better Idea",6);
pc4 =new JustTesting("Heap2",10);
cout<<"Memory contents:\n";
cout<<pc3<<":";
pc3->Show();
cout<<pc4<<":";
pc4->Show();
delete pc2;//释放Heap1
delete pc4;//释放Heap2
pc3->~JustTesting();//销毁指向pc3的对象
pc1->~JustTesting();
delete [] buffer;//释放 buffer
cout << "Done\n";
return 0;
}
以下为输出结果:
JustTesting constructed
Heap1 constructed
Memory block addresses:
buffer:000001B7D04F2A10 heap:000001B7D04E7040
Memory contents:
000001B7D04F2A10:JustTesting , 0
000001B7D04E7040:Heap1 , 20
Better Idea constructed
Heap2 constructed
Memory contents:
000001B7D04F2A40:Better Idea , 6
000001B7D04F2C80:Heap2 , 10
Heap1 destroyed
Heap2 destroyed
Better Idea destroyed
JustTesting destroyed
Done
来源:C++ Primer plus
仅供学习,侵删