目录
定义用户类并实例化为对象
将用户类的声明、实现和使用分别放在三个不同文件里
类的构造函数可以重载
带缺省参数的构造函数
缺省的构造函数
编译器提供的缺省构造函数
编译器不提供缺省构造函数
数组元素类的缺省构造函数
子对象类的缺省构造函数
父对象类的缺省构造函数
支持自定义类型转换的构造函数
拷贝构造函数
编译器提供的缺省拷贝构造函数
用自定义拷贝构造函数取代缺省拷贝构造函数
缺省拷贝构造函数的缺陷
拷贝构造与编译优化
1 定义用户类并实例化为对象
1.1 问题
定义一个用户类User,该类包含两个私有的数据成员,它们是姓名name和年龄age;包含六个公有的成员函数,它们是带参构造函数User,设置姓名函数setName,获取姓名函数getName,设置年龄函数setAge,获取年龄函数getAge,自我介绍函数who。
1.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:定义用户类
代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void);
};
void User::who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
上述代码中,以下代码:
class User
定义了一个类,其中class为关键字,表示开始定义一个类;User为标识符,表示类名。
上述代码中,以下代码:
private:
std::string m_name;
int m_age;
定义了两个私有的数据成员,姓名m_name和年龄m_age。
上述代码中,以下代码:
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void);
定义了五个成员函数,它们是带参构造函数User,设置姓名函数setName,获取姓名函数getName,设置年龄函数setAge,获取年龄函数getAge。
声明了一个成员函数,是自我介绍函数who。该函数的定义在类外实现,如以下代码所示:
void User::who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
在类外定义函数时,注意使用类名加作用域限定符(User::)说明该函数属于哪一个类。
步骤二:将用户类实例化为对象
代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void);
};
void User::who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
int main(int argc, const char * argv[])
{
User user("张飞", 25);
user.who();
user.setName("关羽");
user.setAge(28);
user.who();
return 0;
}
上述代码中,以下代码:
User user("张飞", 25);
定义了一个用户类User的对象user。类是用户自定义的一种数据类型,所以可以直接用类名来定义对象。对象名user后面的括号中的内容为类User的构造函数的实参,当为对象user分配完存储空间后将自动调用类的构造函数。
上述代码中,以下代码:
user.who();
user.setName("关羽");
user.setAge(28);
user.who();
是调用类的成员函数。
1.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void);
};
void User::who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
int main(int argc, const char * argv[])
{
User user("张飞", 25);
user.who();
user.setName("关羽");
user.setAge(28);
user.who();
return 0;
}
2 将用户类的声明、实现和使用分别放在三个不同文件里
2.1 问题
类的声明部分声明了类的成员变量,给出构造函数和成员函数的原型,体现操作接口。类的实现部分给出类的构造函数和成员函数的定义,表达了业务逻辑。类的声明和实现一起,构成了类的完整定义。将类分成声明与实现两部分,是因为类的使用者并不关心亦无需了解类的实现细节,甚至不必或不能看到实现文件,只要包含声明文件即可使用类。
2.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:用户类的声明
在User.h文件中声明用户类User,代码如下:
#ifndef Day03__User
#define Day03__User
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age);
void setName(std::string const& name);
std::string getName();
void setAge(const int& age);
int getAge();
void who (void);
};
#endif /* defined(Day03__User) */
上述代码中,以下代码:
#ifndef Day03__User
#define Day03__User
…
#endif /* defined(Day03__User) */
为条件编译语句,其作用是防止该头文件被重复包含。
步骤二:用户类的实现
在User.cpp文件中声明用户类User,代码如下:
#include “User.h”
User::User(std::string const& name, int age) {
m_name = name;
m_age = age;
}
void User::setName(std::string const& name)
{
m_name = name;
}
std::string User::getName()
{
return m_name;
}
void User::setAge(const int& age)
{
m_age = age;
}
int User::getAge()
{
return m_age;
}
void User::who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
步骤三:用户类的使用
在main.cpp文件中使用用户类User,代码如下:
#include “User.h”
int main(int argc, const char * argv[])
{
User user("张飞", 25);
user.who();
user.setName("关羽");
user.setAge(28);
user.who();
return 0;
}
2.3 完整代码
本案例的完整代码如下所示:
User.h文件
#ifndef Day03__User
#define Day03__User
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age);
void setName(std::string const& name);
std::string getName();
void setAge(const int& age);
int getAge();
void who (void);
};
#endif /* defined(Day03__User) */
User.cpp文件
#include “User.h”
User::User(std::string const& name, int age) {
m_name = name;
m_age = age;
}
void User::setName(std::string const& name)
{
m_name = name;
}
std::string User::getName()
{
return m_name;
}
void User::setAge(const int& age)
{
m_age = age;
}
int User::getAge()
{
return m_age;
}
void User::who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
main.cpp文件
#include “User.h”
int main(int argc, const char * argv[])
{
User user("张飞", 25);
user.who();
user.setName("关羽");
user.setAge(28);
user.who();
return 0;
}
3 类的构造函数可以重载
3.1 问题
因为构造函数带有参数,按照函数重载的概念,只要构造函数的参数类型不同或参数的个数不同,构造函数就可以重载。
3.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:构造函数可以重载
代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User ()
{
m_name = “”;
m_age = 0;
}
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user;
user.setName("关羽");
user.setAge(28);
user.who();
User user1("张飞", 25);
user1.who();
return 0;
}
上述代码中,以下代码:
User ()
{
m_name = "";
m_age = 0;
}
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
构造函数通过参数表的差别化形成重载,第一个构造函数为无参构造函数,第二个构造函数带有两个形参。
上述代码中,以下代码:
User user;
定义了一个类User的对象user,由于在定义时没有构造函数参数,所以调用的是无参构造函数。
上述代码中,以下代码:
User user1("张飞", 25);
定义了一个类User的对象user1,由于在定义时有参数,所以调用的是带参构造函数。
3.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User ()
{
m_name = “”;
m_age = 0;
}
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user;
user.setName("关羽");
user.setAge(28);
user.who();
User user1("张飞", 25);
user1.who();
return 0;
}
4 带缺省参数的构造函数
4.1 问题
因为构造函数带有参数,按照缺省参数的概念,构造函数就可以带有缺省参数。使用缺省参数可以减少构造函数重载版本的数量,但是使用缺省参数时应注意避免与重载冲突。
4.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:带缺省参数的构造函数
代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name = “”, int age = 0)
{
m_name = name;
m_age = age;
}
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user;
user.setName("关羽");
user.setAge(28);
user.who();
User user1("张飞", 25);
user1.who();
return 0;
}
上述代码中,以下代码:
User (std::string const& name = "", int age = 0)
{
m_name = name;
m_age = age;
}
定义了一个带有缺省参数的构造函数。值得注意的是,如果定义的此构造函数,就不要再重载无参构造函数了,如下代码所示:
User ()
{
m_name = "";
m_age = 0;
}
因为无参构造函数与此构造函数冲突。
4.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name = “”, int age = 0)
{
m_name = name;
m_age = age;
}
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user;
user.setName("关羽");
user.setAge(28);
user.who();
User user1("张飞", 25);
user1.who();
return 0;
}
5 缺省的构造函数
5.1 问题
缺省构造函数亦称无参构造函数,但其未必真的没有任何参数,为一个有参构造函数的每个参数都提供一个缺省值,同样可以达到无参构造函数的效果。
5.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:缺省构造函数
代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User ()
{
m_name = “”;
m_age = 0;
}
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user;
user.setName("关羽");
user.setAge(28);
user.who();
return 0;
}
上述代码中,以下代码:
User ()
{
m_name = "";
m_age = 0;
}
定义了一个缺省构造函数,该构造函数没有参数。值得注意的是,带参构造函数的每个参数如果都有缺省值,如下代码所示:
User (std::string const& name = "", int age = 0)
{
m_name = name;
m_age = age;
}
也可以称为缺省构造函数,因为当没有实参时,缺省值可以代替实参。
5.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User ()
{
m_name = “”;
m_age = 0;
}
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user;
user.setName("关羽");
user.setAge(28);
user.who();
return 0;
}
6 编译器提供的缺省构造函数
6.1 问题
如果一个类没有定义任何构造函数,那么编译器会为其提供一个缺省构造函数,该构造函数对基本类型的成员变量,不做初始化;对类类型的成员变量和基类子对象,调用相应类型的缺省构造函数初始化。编译器提供的构造函数不会出现在源文件中,而是直接生成完成特定功能的机器(汇编)指令。
6.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:编译器提供的缺省构造函数
代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user;
user.setName("关羽");
user.setAge(28);
user.who();
return 0;
}
上述代码中,没有提供构造函数,而以下语句:
User user;
在为对象user分配完存储空间之后,仍然会调用类User的构造函数,这个被调用的构造函数是编译器为无构造函数的类User自动添加的,其形式如下所示:
User::User ()
{
}
6.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user;
user.setName("关羽");
user.setAge(28);
user.who();
return 0;
}
7 编译器不提供缺省构造函数
7.1 问题
对于已经定义至少一个构造函数的类,无论其构造函数是否带有参数,编译器都不会再为其提供缺省构造函数。这种情况下,若该类需要支持以缺省方式构造对象,则必须自己定义缺省构造函数,否则将导致编译错误。
7.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:编译器不提供缺省构造函数
代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张飞", 25);
user.who();
User user1;
user1.setName("关羽");
user1.setAge(28);
user1.who();
return 0;
}
上述代码中,由于提供了带参构造函数,编译器就不会再为类User添加缺省构造函数了。这样,上述代码中,以下代码:
User user1;
就会出现编译错误,为防止此类错误,应添加以下缺省构造函数代码:
User ()
{
m_name = "";
m_age = 0;
}
7.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User ()
{
m_name = “”;
m_age = 0;
}
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void setName(std::string const& name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
void setAge(const int& age)
{
m_age = age;
}
int getAge()
{
return m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张飞", 25);
user.who();
User user1;
user1.setName("关羽");
user1.setAge(28);
user1.who();
return 0;
}
8 数组元素类的缺省构造函数
8.1 问题
在使用数组或容器的时候,经常不对元素进行初始化。由于数组本质上就是多个对象的集合,每个对象在分配完存储空间后都将调用对应类的构造函数。所以,类中应有缺省构造函数。
8.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:数组元素类的缺省构造函数
代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User ()
{
m_name = “”;
m_age = 0;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user[5];
for (int i = 0; i < 5; i++)
user[i].who();
return 0;
}
上述代码中,以下代码:
User user[5];
定义了一个类User的对象数组user,该数组有5个数组元素。每个数组元素在分配完存储空间之后,将调用类User的缺省构造函数。
8.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User ()
{
m_name = “”;
m_age = 0;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user[5];
for (int i = 0; i < 5; i++)
user[i].who();
return 0;
}
9 子对象类的缺省构造函数
9.1 问题
当一个类中含有另一个类的对象作为数据成员时,有时必须为一个类提供缺省构造函数。因为当给类的对象分配存储空间时,类中的对象数据成员分配完存储空间之后马上会调用对应类的缺省构造函数。
9.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:数组元素类的缺省构造函数
代码如下:
#include
class Date
{
private:
int m_year;
int m_mon;
int m_day;
public:
Date (void)
{
m_year=1970;
m_mon=1;
m_day=1;
}
void show(void)
{
std::cout << m_year << “年” << m_mon << “月” << m_day << “日” << std::endl;
}
};
class User
{
private:
std::string m_name;
int m_age;
Date m_birthday;
public:
User ()
{
m_name = “张三”;
m_age = 20;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
std::cout << “出生日期:”;
m_birthday.show();
std::cout << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user;
user.who();
return 0;
}
上述代码中,以下代码:
Date m_birthday;
在类User中含有另一个类Date的对象作为数据成员。在如下语句中:
User user;
定义了类User的对象user。当程序运行到此语句时,会为对象user分配存储空间,首先为m_name分配存储空间,然后为m_age分配存储空间,当为m_birthday分配完存储空间之后,由于m_birthday是对象,所以分配完存储空间后会马上调用类Date的缺省构造函数。正是由于这个原因,当一个类中含有另一个类的对象作为数据成员时,有时必须为一个类提供缺省构造函数。
9.3 完整代码
本案例的完整代码如下所示:
#include
class Date
{
private:
int m_year;
int m_mon;
int m_day;
public:
Date (void)
{
m_year=1970;
m_mon=1;
m_day=1;
}
void show(void)
{
std::cout << m_year << “年” << m_mon << “月” << m_day << “日” << std::endl;
}
};
class User
{
private:
std::string m_name;
int m_age;
Date m_birthday;
public:
User ()
{
m_name = “张三”;
m_age = 20;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
std::cout << “出生日期:”;
m_birthday.show();
std::cout << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user;
user.who();
return 0;
}
10 父对象类的缺省构造函数
10.1 问题
如果出于某种原因,对象的对象数据成员确实不宜缺省构造,那么也可以为对象提供自定义的缺省构造函数,在其中显式地以非缺省方式调用对象数据成员的构造函数。
10.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:父对象类的缺省构造函数
代码如下:
#include
class Date
{
private:
int m_year;
int m_mon;
int m_day;
public:
Date (int year, int mon, int day)
{
m_year = year;
m_mon = mon;
m_day = day;
}
void show(void)
{
std::cout << m_year << “年” << m_mon << “月” << m_day << “日” << std::endl;
}
};
class User
{
private:
std::string m_name;
int m_age;
Date m_birthday;
public:
User ():m_birthday(1970,1,1)
{
m_name = “张三”;
m_age = 20;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
std::cout << “出生日期:”;
m_birthday.show();
std::cout << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user;
user.who();
return 0;
}
上述代码中,假设类Date出于某种原因,确实不宜缺省构造,必须是带参构造,代码如下所示:
Date (int year, int mon, int day)
{
m_year = year;
m_mon = mon;
m_day = day;
}
那么,在类User中又含有类Date的对象的数据成员,代码如下所示:
Date m_birthday;
这样,当给m_birthday分配完存储空间之后,应该马上调用类Date的构造函数,但因为类Date已经有自定义的带参构造函数,所以编译器就不会自动添加缺省构造函数了,但调用带参构造函数必须给出实参,因此必须显式地以非缺省方式调用带参构造函数,代码如下所示:
User ():m_birthday(1970,1,1)
{
m_name = "张三";
m_age = 20;
}
10.3 完整代码
本案例的完整代码如下所示:
#include
class Date
{
private:
int m_year;
int m_mon;
int m_day;
public:
Date (int year, int mon, int day)
{
m_year = year;
m_mon = mon;
m_day = day;
}
void show(void)
{
std::cout << m_year << “年” << m_mon << “月” << m_day << “日” << std::endl;
}
};
class User
{
private:
std::string m_name;
int m_age;
Date m_birthday;
public:
User ():m_birthday(1970,1,1)
{
m_name = “张三”;
m_age = 20;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
std::cout << “出生日期:”;
m_birthday.show();
std::cout << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user;
user.who();
return 0;
}
11 支持自定义类型转换的构造函数
11.1 问题
所谓类型转换,其本质都是在创建新对象,该对象的类型由目标类型决定,内容则源自被转换对象,而被转换对象的类型和内容在转换前后不会发生任何变化。
在C++中, 单参构造函数除了是个构造器,还是个默认且隐含的类型转换操作符。
11.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:支持自定义类型转换的构造函数
代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
User (const char* str)
{
m_name = str;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张三");
user.who();
User user1 = "王五";
user1.who();
return 0;
}
上述代码中,以下代码:
User (const char* str)
{
m_name = str;
}
是一个单参构造函数,当以下语句:
User user("张三");
被执行时,这个单参构造函数会被调用,即作为一个构造器来使用。但单参构造函数除了可以作为构造器使用外,还是个默认且隐含的类型转换操作符,如下代码所示:
User user1 = "王五";
这个单参构造函数会将右值const char*类型的"王五"默认地转换成一个User类型的临时对象,再将该临时对象赋值给user1。
11.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
User (const char* str)
{
m_name = str;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张三");
user.who();
User user1 = "王五";
user1.who();
return 0;
}
12 拷贝构造函数
12.1 问题
拷贝构造函数,是一种特殊的构造函数,用于从一个已定义的对象构造其同类型的副本对象,即对象克隆。
12.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:拷贝构造函数
代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
User (User const &user)
{
m_name = user.m_name;
m_age = user.m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张三", 25);
user.who();
User user1 = user;
user1.who();
return 0;
}
上述代码中,以下代码:
User (User const &user)
{
m_name = user.m_name;
m_age = user.m_age;
}
为拷贝构造函数,其唯一的形参必须是引用,但并不限制为const,一般普遍的会加上const限制。当执行以下与赋值有关的语句时:
User user1 = user;
拷贝构造函数会被调用。
12.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
User (User const &user)
{
m_name = user.m_name;
m_age = user.m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张三", 25);
user.who();
User user1 = user;
user1.who();
return 0;
}
13 编译器提供的缺省拷贝构造函数
13.1 问题
如果一个类没有定义拷贝构造函数,那么编译器会为其提供一个缺省拷贝构造函数。在缺省拷贝构造函数中,对基本类型的成员变量,按字节复制;对类类型的成员变量和基类子对象,调用相应类型的拷贝构造函数。
13.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:编译器提供的缺省拷贝构造函数
代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张三", 25);
user.who();
User user1 = user;
user1.who();
return 0;
}
上述代码中没有定义拷贝构造函数,那么编译器会为类User提供一个缺省拷贝构造函数,所以如下语句:
User user1 = user;
仍然能够完成制作副本的工作。只不过完成的过程是这样的:
std::string m_name;
int m_age;
对于类User的两个数据成员,m_name会调用类std::string的拷贝构造函数,而m_age会按字节拷贝的方式复制。
13.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张三", 25);
user.who();
User user1 = user;
user1.who();
return 0;
}
14 用自定义拷贝构造函数取代缺省拷贝构造函数
14.1 问题
如果自己定义了拷贝构造函数,那么编译器将不再提供缺省拷贝构造函数。这种情况下,所有与对象复制有关的操作,都必须在自定义拷贝构造函数中通过编写代码完成。
14.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:用自定义拷贝构造函数取代缺省拷贝构造函数
代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
User (User const &user)
{
m_name = user.m_name;
m_age = user.m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张三", 25);
user.who();
User user1 = user;
user1.who();
return 0;
}
上述代码中,以下代码:
User (User const &user)
{
m_name = user.m_name;
m_age = user.m_age;
}
为自定义拷贝构造函数,此时编译器将不再提供缺省拷贝构造函数。这样如何复制一个当前类的对象就完全取决于自定义拷贝构造函数的函数体如何编写。
14.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
User (User const &user)
{
m_name = user.m_name;
m_age = user.m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张三", 25);
user.who();
User user1 = user;
user1.who();
return 0;
}
15 缺省拷贝构造函数的缺陷
15.1 问题
多数情况下,编译器提供的缺省拷贝构造函数已足够适用,这时无需自己定义拷贝构造函数。特殊情况下,编译器提供的缺省拷贝构造函数可能无法满足要求,这时就必须通过自定义拷贝构造函数,实现特殊的对象复制语义,以弥补缺省拷贝构造函数的缺陷。
15.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:缺省拷贝构造函数的缺陷
代码如下:
#include
#include
class User
{
private:
char* m_name;
int m_age;
public:
User (char const* name, int age)
{
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
m_age = age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张三", 25);
user.who();
User user1 = user;
user1.who();
return 0;
}
上述代码中,以下代码:
private:
char* m_name;
int m_age;
表示类User中定义了两个数据成员,一个是字符指针m_name,另一个是整型变量m_age,它们属于基本类型的成员变量。因此,缺省拷贝构造函数在实现类User的对象复制时,对上述两个数据成员均采用字节复制的方法。如下代码所示:
User user1 = user;
复制的结果是user1.m_name指向了user.m_name指向的空间,是user.m_name指针的浅拷贝。如果user对象先释放了,则user1.m_name将变成野指针。这显然不是我们希望的结果,为了避免这种情况的发生,就必须自定义拷贝构造函数,代码如下:
User (User const &user)
{
m_name = new char[strlen(user.m_name) + 1];
strcpy(m_name, user.m_name);
m_age = user.m_age;
}
为user1.m_name再在堆上分配一块儿存储空间,实现真正意义上的深拷贝。这样即使user对象先释放了,user1.m_name指向的空间仍然存在。
15.3 完整代码
本案例的完整代码如下所示:
#include
#include
class User
{
private:
char* m_name;
int m_age;
public:
User (char const* name, int age)
{
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
m_age = age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张三", 25);
user.who();
User user1 = user;
user1.who();
return 0;
}
16 拷贝构造与编译优化
16.1 问题
拷贝构造过程通常被使用在以下三种情况:用已定义的对象作为同类型对象的构造实参;一个对象作为函数参数,以值传递的方式传入函数体;从函数中返回对象。
拷贝构造过程风险高而效率低,设计时应尽可能避免。编译器会通过必要的优化策略,减少拷贝构造的机会。
16.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:拷贝构造与编译优化
代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
User (User const &user)
{
m_name = user.m_name;
m_age = user.m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
void print(User user)
{
user.who();
}
User create()
{
User obj(“张三”, 25);
return obj;
}
int main(int argc, const char * argv[])
{
User user = create();
print(user);
User user1 = user;
print(user);
return 0;
}
上述代码中,以下代码:
void print(User user)
{
user.who();
}
类User的对象user作为形参值传递给函数print。当调用该函数时:
print(user);
相当于将实参赋值给形参。形参需要分配一块儿与实参相同大小的存储空间,然后通过拷贝构造函数再将实参的数据拷贝到形参当中去,这一过程效率极低。一般应使用引用方法代替。
上述代码中,以下代码:
User create()
{
User obj(“张三”, 25);
return obj;
}
函数create返回一个类User的对象。效率也是极低的,原因同上,应当避免。
16.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
User (User const &user)
{
m_name = user.m_name;
m_age = user.m_age;
}
void who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
void print(User user)
{
user.who();
}
User create()
{
User obj(“张三”, 25);
return obj;
}
int main(int argc, const char * argv[])
{
User user = create();
print(user);
User user1 = user;
print(user);
return 0;
}