C++会自动提供下面这些成员函数
- 默认构造函数,如果没有定义构造函数
- 默认析构函数,如果没有定义
- 复制构造函数,如果没有定义
- 赋值运算符,如果没有定义
- 地址运算符,如果没有定义
1、默认构造函数
- 如果没有提供任何构造函数,C++会创建默认的构造函数。比如,一个类名为 Name,则编译器会提供一个默认构造函数Name::Name()
创建对象时总是会调用默认构造函数。这个默认构造函数没有任何参数值。
- 也可以提供一个带有参数的默认构造函数,但是参数必须有默认值。比如:Name(int n=0) {num = n;}。但是类声明中不能同时包含这两个函数。
2、复制构造函数
- 复制构造函数用于将一个对象赋值到新创建的对象中。也就是说,它用于初始化的过程中(包括按值传递参数),而不是常规的赋值过程中。类的复制构造函数的原型是
类名 (const 类名 &)
- 何时调用复制构造函数
新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。
一下四种情况都会调用复制构造函数
StringBad str1(str2);
StringBad str3 = str2;
StringBad str4 = StringBad(str2);
StringBad * pstr5 = new StringBad(str2);
每当程序生成了对象副本时,编译器都将只用复制构造函数。具体讲,当函数按值传递对象或者函数返回对象时,都将使用复制构造函数。按值传递意味着创建原始变量的一个副本,编译器生成临时对象时,也将使用复制构造函数。
由于按值传递会调用复制构造函数,因此应该按引用来传递对象,这样会节省调用构造函数的时间以及存储新对象的空间。
- 默认复制构造函数的功能
默认的复制构造函数逐个赋值非静态成员(成员复制也称为浅复制),复制的是成员的值。
缺点:
默认复制构造函数不能更新静态变量的值,也不能实现字符串的复制。因为它是浅复制,只能实现指向字符串的指针的值的复制。
解决方法:重新定义一个显式复制构造函数,实现深度复制。
复制构造函数应该复制字符串并将副本的地址赋给指针成员,而不仅仅是复制字符串的地址。这样每个对象就都有自己的字符串,而不是引用另一个对象的字符串。调用析构函数的时候都将释放不同的字符串,而不是试图去释放已经被释放的字符串。
示例:
StringBad::StringBad(const StringBad & st)
{
num_strings++;
len = st.len;
str = new char[len + 1];
strcpy(str, st.str);
}
3、赋值运算符
C++允许类对象赋值,这是通过自动为类重载赋值运算符来实现的。这种函数的原型是:
类名 & 类名::operator=(const 类名 &);
- 赋值运算符的功能
①
将已有对象赋给另一个对象时,将使用重载的赋值运算符。
str1 = str2;这一种情况下肯定会使用重载赋值运算符
②初始化对象时,一定会使用复制构造函数,因为有=,所以也可能会使用赋值运算符。
String str1 = str2;str1是一个新创建的对象,被初始化为str2的值,因此使用复制构造函数。这个语句实现的步骤,使用复制构造函数创建一个临时对象,然后通过赋值将临时对象的值复制到新对象。
- 赋值运算符的缺点
此时和复制构造函数的缺点是一样的,赋值是浅复制。
- 赋值运算符的解决方法
StringBad & StringBad::operator=(const StringBad & st)
{
if(this == &st)
return *this;
delete [] str;//释放当前字符串的以前的内存
len = st.len;
str = new char[len+1];
strcpy(str,st.str);
}
四、一个因为没有改变默认复制构造函数和默认赋值运算符而出现错误的程序
#ifndef STRINGBAD_H_
#define STRINGBAD_H_
#include <iostream>
class StringBad
{
private:
char * str;
int len;
static int num_strings;
/*
静态类成员,无论创建多少对象,程序都只创建一个静态类变量的副本。
也就是说类的所有对象共享同一个静态成员。
num_strings记录的是所创建对象的数目
*/
public:
StringBad(const char * s);
StringBad();
~StringBad();
//友元函数
friend std::ostream & operator<<(std::ostream & os, const StringBad & st);
};
#endif
#include "stringbad.h"
#include <cstring>
using namespace std;
int StringBad::num_strings = 0;
StringBad::StringBad(const char * s)
{
len = std::strlen(s);
str = new char[len + 1];
strcpy(str,s);
num_strings++;
cout << num_strings << ":\"" << str << "\" 创建。\n";
}
StringBad::StringBad()
{
len = 4;
str = new char[4];
strcpy(str,"C++");
num_strings++;
cout << num_strings << ":\"" << str << "\" 被创建。\n";
}
StringBad::~StringBad()
{
cout << "\"" << str << "\" 被删除, ";
--num_strings;
cout << num_strings << " 剩余。\n";
delete [] str;
}
std::ostream & operator<<(std::ostream & os, const StringBad & st)
{
os << st.str;
return os;
}
#include "stringbad.h"
#include <iostream>
using namespace std;
void callme1(StringBad &);//通过引用传递,然后输出
void callme2(StringBad);//通过值传递,然后输出
int main()
{
{
cout << "开始创建字符串:\n";
StringBad str1("字符串1");
StringBad str2("字符串2");
StringBad str3("字符串3");
cout << "str1: " << str1 << endl;
cout << "str2: " << str2 << endl;
cout << "str3: " << str3 << endl;
callme1(str1);
cout << "str1: " << str1 << endl;
callme2(str2);
cout << "str2: " << str2 <<endl;
StringBad str4 = str1;
/*
StringBad str4 = str1;等效于StringBad str4 = StringBad(str1);
因为str1的类型是StringBad,因此相应的构造函数原型应该是
StringBad(const StringBad &);
当使用一个对象来初始化另一个对象时,编译器将自动生成上述构造函数(复制构造函数),
因为它创建一个对象的副本。自动生成的构造函数不知道需要更新静态变量,num_strings,
因此会引起计数的错误。
*/
cout << "str4: " << str4 << endl;
StringBad str5;
str5 = str2;
cout << "str5: " << str5 << endl;
}
cin.get();
return 0;
}
void callme1(StringBad & rsb)
{
cout << "字符串通过引用来传递:";
cout<< "\"" << rsb << "\"\n";
}
void callme2(StringBad sb)
{
cout << "字符串通过值传递:";
cout << "\"" << sb << "\"\n";
}
五、加入复制构造函数和赋值运算符之后的程序
#ifndef STRINGBAD_H_
#define STRINGBAD_H_
#include <iostream>
class StringBad
{
private:
char * str;
int len;
static int num_strings;
/*
静态类成员,无论创建多少对象,程序都只创建一个静态类变量的副本。
也就是说类的所有对象共享同一个静态成员。
num_strings记录的是所创建对象的数目
*/
public:
StringBad(const char * s);
StringBad();
~StringBad();
//友元函数
friend std::ostream & operator<<(std::ostream & os, const StringBad & st);
StringBad & operator=(const StringBad & st);//赋值运算符
StringBad(const StringBad & st);//复制构造函数
};
#endif
#include "stringbad.h"
#include <cstring>
using namespace std;
int StringBad::num_strings = 0;
StringBad::StringBad(const char * s)
{
len = std::strlen(s);
str = new char[len + 1];
strcpy(str,s);
num_strings++;
cout << num_strings << ":\"" << str << "\" 创建。\n";
}
StringBad::StringBad()
{
len = 4;
str = new char[4];
strcpy(str,"C++");
num_strings++;
cout << num_strings << ":\"" << str << "\" 被创建。\n";
}
StringBad::~StringBad()
{
cout << "\"" << str << "\" 被删除, ";
--num_strings;
cout << num_strings << " 剩余。\n";
delete [] str;
}
std::ostream & operator<<(std::ostream & os, const StringBad & st)
{
os << st.str;
return os;
}
StringBad & StringBad::operator=(const StringBad & st)//赋值运算符
{
if(this == &st)
return *this;
delete [] str;//释放当前字符串的以前的内存
len = st.len;
str = new char[len+1];
strcpy(str,st.str);
}
StringBad::StringBad(const StringBad & st)//复制构造函数
{
num_strings++;
len = st.len;
str = new char[len + 1];
strcpy(str, st.str);
cout << num_strings << ":\"" << str << "\" 创建。\n";
}
#include "stringbad.h"
#include <iostream>
using namespace std;
void callme1(StringBad &);//通过引用传递,然后输出
void callme2(StringBad);//通过值传递,然后输出
int main()
{
{
cout << "开始创建字符串:\n";
StringBad str1("字符串1");
StringBad str2("字符串2");
StringBad str3("字符串3");
cout << "str1: " << str1 << endl;
cout << "str2: " << str2 << endl;
cout << "str3: " << str3 << endl;
callme1(str1);
cout << "str1: " << str1 << endl;
callme2(str2);
cout << "str2: " << str2 <<endl;
StringBad str4 = str1;
cout << "str4: " << str4 << endl;
/*
StringBad str4 = str1;等效于StringBad str4 = StringBad(str1);
因为str1的类型是StringBad,因此相应的构造函数原型应该是
StringBad(const StringBad &);
当使用一个对象来初始化另一个对象时,编译器将自动生成上述构造函数(复制构造函数),
因为它创建一个对象的副本。自动生成的构造函数不知道需要更新静态变量,num_strings,
因此会引起计数的错误。
*/
StringBad str5;
str5 = str2;
cout << "str5: " << str5 << endl;
}
cin.get();
return 0;
}
void callme1(StringBad & rsb)
{
cout << "字符串通过引用来传递:";
cout<< "\"" << rsb << "\"\n";
}
void callme2(StringBad sb)
{
cout << "字符串通过值传递:";
cout << "\"" << sb << "\"\n";
}