目录:
带初始化表的构造函数
数组和结构型成员的初始化
类类型成员的初始化
常量和引用型成员的初始化
初始化顺序
隐藏的this指针参数
通过this指针区分成员
返回调用对象
教师和学生
常函数和mutable关键字
通过常/非常对象调用常/非常函数
常函数与非常函数的重载匹配
析构函数的基本用法
析构函数负责总善后
缺省析构函数不释放动态分配的资源
缺省拷贝构造函数仅支持浅拷贝
自定义拷贝构造函数支持深拷贝
支持深拷贝的拷贝赋值运算符函数
1 带初始化表的构造函数
1.1 问题
通过在类的构造函数中使用初始化表,指明该类的成员变量如何被初始化。
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 who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张三", 20);
user.who();
return 0;
}
上述代码中,以下代码:
User (std::string const& name, int age):m_name(name), m_age(age)
{
}
定义了一个带初始化表的构造函数,冒号:后面的内容被称为初始化列表,当初始化列表中含有多个数据成员时,用逗号,分开。使用初始化列表的构造函数显式的初始化类的成员。如,当数据成员m_name分配完存储空间之后,会马上到初始化列表中寻找m_name(name),如果存在则调用对应的构造函数,否则调用类std::string的缺省构造函数。如果将自定义构造函数写成如下所示:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
则数据成员m_name分配完存储空间之后,因为初始化列表为空,所以直接调用类std::string的缺省构造函数。当所有数据成员均构造完成之后,在对m_name进行赋值操作。显然在初始化列表中,直接调用对应的构造函数效率更高。
对于基本数据类型的数据成员,上述两种方法没有区别,但习惯上还是使用初始化列表的方法。
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 who (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张三", 20);
user.who();
return 0;
}
2 数组和结构型成员的初始化
2.1 问题
数组和结构体型的成员变量在类构造函数的初始化表中需要用花括号“{}”初始化。
2.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:数组和结构型成员的初始化
代码如下:
#include
struct Curriculum
{
std::string subject;
int period;
};
class Teacher
{
private:
std::string m_students[3];
Curriculum m_curriculum;
public:
Teacher (std::string const& s1, std::string const& s2, std::string const& s3, std::string const& subject, const int& period) : m_students {s1, s2, s3}, m_curriculum {subject, period}
{
}
void show (void)
{
std::cout << “学科:” << m_curriculum.subject << “,周期:” << m_curriculum.period << “天” << std::endl;
std::cout << “学生姓名:”;
for (int i = 0; i < 3; i++)
std::cout << m_students[i] << " ";
std::cout << std::endl;
}
};
int main(int argc, const char * argv[])
{
Teacher t("张三", "李四", "王五", "标准C++", 9);
t.show();
return 0;
}
上述代码中,以下代码:
private:
std::string m_students[3];
Curriculum m_curriculum;
定义了类Teacher的两个数据成员,一个是std::string类型的数组m_students,另一个是Curriculum类型的结构体变量m_curriculum。因为m_students数组因为有三个元素,所以在初始化列表中需要将三个元素用大括号括起来,如下代码所示:
Teacher (std::string const& s1, std::string const& s2, std::string const& s3, std::string const& subject, const int& period) : m_students {s1, s2, s3}, m_curriculum {subject, period}
{
}
又因为结构体变量m_curriculum有两个成员,所以在初始化列表中也需要用大括号将两个成员的初始化值括起来。
2.3 完整代码
本案例的完整代码如下所示:
#include
struct Curriculum
{
std::string subject;
int period;
};
class Teacher
{
private:
std::string m_students[3];
Curriculum m_curriculum;
public:
Teacher (std::string const& s1, std::string const& s2, std::string const& s3, std::string const& subject, const int& period) : m_students {s1, s2, s3}, m_curriculum {subject, period}
{
}
void show (void)
{
std::cout << “学科:” << m_curriculum.subject << “,周期:” << m_curriculum.period << “天” << std::endl;
std::cout << “学生姓名:”;
for (int i = 0; i < 3; i++)
std::cout << m_students[i] << " ";
std::cout << std::endl;
}
};
int main(int argc, const char * argv[])
{
Teacher t("张三", "李四", "王五", "标准C++", 9);
t.show();
return 0;
}
3 类类型成员的初始化
3.1 问题
类的类类型成员变量和基类子对象,要么在初始化表中显式初始化,要么通过相应类型的缺省构造函数初始化。
3.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:类类型成员的初始化
代码如下:
#include
class Date
{
private:
int m_year;
int m_mon;
int m_day;
public:
Date (const int& year, const int& mon, const 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 (std::string const& name, const int& age, const int& year, const int& mon, const int& day):m_name(name), m_age(age), m_birthday(year, mon, day)
{
}
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("张三", 20, 1970, 1, 1);
user.who();
return 0;
}
上述代码中,以下代码:
Date m_birthday;
在类User中有一个类Data的对象作为类User类型成员变量,该成员变量需要在类User构造函数的初始化表中显式初始化,代码如下所示:
User (std::string const& name, const int& age, const int& year, const int& mon, const int& day):m_name(name), m_age(age), m_birthday(year, mon, day)
{
}
其中,m_birthday(year, mon, day)为像是初始化的内容。
3.3 完整代码
本案例的完整代码如下所示:
#include
class Date
{
private:
int m_year;
int m_mon;
int m_day;
public:
Date (const int& year, const int& mon, const 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 (std::string const& name, const int& age, const int& year, const int& mon, const int& day):m_name(name), m_age(age), m_birthday(year, mon, day)
{
}
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("张三", 20, 1970, 1, 1);
user.who();
return 0;
}
4 常量和引用型成员的初始化
4.1 问题
当一个类中有常量型和引用型成员变量时,必须在初始化表中显式初始化,不能在构造函数体中赋初值。
4.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:常量和引用型成员的初始化
代码如下:
#include
class Circle {
private:
double const m_pi; // 常量型成员变量
double& m_r; // 引用型成员变量
public:
Circle (double pi, double& r) : m_pi (pi), m_r ®
{
}
double perimeter (void)
{
return 2 * m_pi * m_r;
}
double area (void)
{
return m_pi * m_r * m_r;
}
};
int main(int argc, const char * argv[])
{
double r = 10.0;
Circle cir(3.14, r);
std::cout << "周长:" << cir.perimeter() << std::endl;
std::cout << "面积:" << cir.area() << std::endl;
return 0;
}
上述代码中,以下代码:
private:
double const m_pi; // 常量型成员变量
double& m_r; // 引用型成员变量
在类Circle中定义了两个私有的成员变量,一个是常量型成员变量m_pi,另一个是引用型成员变量m_r。我们知道,常量和引用在定义时必须初始化,那么类中的常量和引用数据成员由于是在定义对象时分配存储空间,所以它们的初始化工作也被放到构造函数中完成,只不过要求必须在初始化列表中初始化,代码如下所示:
Circle (double pi, double& r) : m_pi (pi), m_r (r)
{
}
其中,m_pi (pi)是对常量型成员变量的初始化,m_r ®是对引用性成员变量的初始化。
4.3 完整代码
本案例的完整代码如下所示:
#include
class Circle {
private:
double const m_pi; // 常量型成员变量
double& m_r; // 引用型成员变量
public:
Circle (double pi, double& r) : m_pi (pi), m_r ®
{
}
double perimeter (void)
{
return 2 * m_pi * m_r;
}
double area (void)
{
return m_pi * m_r * m_r;
}
};
int main(int argc, const char * argv[])
{
double r = 10.0;
Circle cir(3.14, r);
std::cout << "周长:" << cir.perimeter() << std::endl;
std::cout << "面积:" << cir.area() << std::endl;
return 0;
}
5 初始化顺序
5.1 问题
类的成员变量按其在类中的声明顺序依次被初始化,而与其在初始化表中的排列顺序无关。
5.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:初始化顺序
代码如下:
#include
class Message
{
private:
size_t m_len;
std::string m_str;
public:
Message (std::string const& str) : m_str (str), m_len (m_str.length ())
{
}
void show (void)
{
std::cout << m_str << std::endl;
}
size_t size (void)
{
return m_len;
}
};
int main(int argc, const char * argv[])
{
Message m("Hello World!");
m.show();
return 0;
}
上述代码中,以下代码:
private:
size_t m_len;
std::string m_str;
public:
Message (std::string const& str) : m_str (str), m_len (m_str.length ())
{
}
在类Message中定义了两个成员变量m_len和m_str,并在构造函数的初始化列表中对它们进行初始化。那么两个成员变量先初始化哪一个,是按其在类中的声明顺序依次被初始化,声明顺序是指以下代码:
private:
size_t m_len;
std::string m_str;
还有一个顺序是其在初始化表中的排列顺序,是指以下代码:
Message (std::string const& str) : m_str (str), m_len (m_str.length ())
{
}
该顺序与变量的初始化顺序无关。其原因是,当定义一个类的对象时,会首先给该对象的所有数据成员分配存储空间,这些数据成员在分配空间时是按照声明的顺序来逐个分配的,每分配一个数据成员就会马上对其进行初始化。
5.3 完整代码
本案例的完整代码如下所示:
#include
class Message
{
private:
size_t m_len;
std::string m_str;
public:
Message (std::string const& str) : m_str (str), m_len (m_str.length ())
{
}
void show (void)
{
std::cout << m_str << std::endl;
}
size_t size (void)
{
return m_len;
}
};
int main(int argc, const char * argv[])
{
Message m("Hello World!");
m.show();
return 0;
}
6 隐藏的this指针参数
6.1 问题
相同类型的不同对象各自拥有独立的存储空间,但它们在代码区中彼此共享同一份成员函数代码。这样就带来一个问题,就是函数代码如何区分所访问的成员变量隶属于哪个对象?因此,编译器为类的每个成员函数添加一个隐藏的指针型参数this,指向调用该成员函数的对象,这就是this指针。
6.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:隐藏的this指针参数
代码如下:
#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 print (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张飞", 25);
user.print();
return 0;
}
上述代码中,以下代码:
void print (void)
{
std::cout << "我是" << m_name <<",今年" << m_age << "岁。" << std::endl;
}
是类User的一个成员函数,该函数没有形参。编译时,编译器会为该函数添加一个形参,如下代码所示:
void _ZN4User5printEv (User* this)
{
std::cout << "我是" << this->m_name <<",今年" << this->m_age << "岁。" << std::endl;
}
该形参就是一个本类的指针this。在该函数的调用处:
user.print();
编译器会相应的添加一个实参,该实参就是调用函数的对象的地址&user。
类的构造和析构函数中同样有this指针,指向这个正在被构造或析构的对象。
6.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 print (void)
{
std::cout << “我是” << m_name <<",今年" << m_age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张飞", 25);
user.print();
return 0;
}
7 通过this指针区分成员
7.1 问题
多数情况下,程序并不需要显式地使用this指针,编译器通过名字解析,可以判断出所访问的标识符是否属于该类的成员,是则为其加上“this->”。
为了提高代码的可读性,常将类的构造函数的参数,与被该参数初始化的成员取成相同的名称,在构造函数内部,除了使用初始化表外,还可以通过this指针,将同名的成员与非成员区分开来。
7.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:通过this指针区分成员
代码如下:
#include
class User
{
private:
std::string name;
int age;
public:
User (std::string const& name, int age)
{
this->name = name;
this->age = age;
}
void print (void)
{
std::cout << “我是” << name <<",今年" << age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张飞", 25);
user.print();
return 0;
}
上述代码中,以下代码:
private:
std::string name;
int age;
public:
User (std::string const& name, int age)
{
this->name = name;
this->age = age;
}
在类User中定义了两个数据成员name和age,并在构造函数中对其进行初始化。为了提高代码的可读性,构造函数的参数名也起成name和age,但这样,属于局部变量的形参将隐藏类的成员变量,使之不起作用。此时如果在构造函数体内对类的成员变量进行操作的话,就可以使用this指针对形参和成员变量进行区分。
7.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string name;
int age;
public:
User (std::string const& name, int age)
{
this->name = name;
this->age = age;
}
void print (void)
{
std::cout << “我是” << name <<",今年" << age << “岁。” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张飞", 25);
user.print();
return 0;
}
8 返回调用对象
8.1 问题
在类的成员函数中,通过this指针向该成员函数的调用者可以返回调用对象的自拷贝或自引用。
8.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:返回调用对象
代码如下:
#include
class Integer
{
private:
int m_value;
public:
Integer(const int& integer):m_value(integer)
{
}
Integer add(Integer const& that)
{
m_value += that.m_value;
return *this; // 返回自拷贝
}
Integer& inc (void)
{
++m_value;
return *this; // 返回自引用
}
void show (void)
{
std::cout << m_value << std::endl;
}
};
int main(int argc, const char * argv[])
{
Integer inte1(10);
Integer inte2(20);
Integer inte3(30);
inte1 = inte1.add(inte2).add(inte3);
inte1.show();
inte1.inc().inc();
inte1.show();
return 0;
}
上述代码中,以下代码:
Integer add(Integer const& that)
{
m_value += that.m_value;
return *this; // 返回自拷贝
}
在类Integer中定义了一个成员函数add,该函数返回一个类Integer的对象,由于this指针指向的是调用该函数的对象,那么*this就是调用该函数的对象本身。这样的返回值可以起到如下代码的作用:
inte1 = inte1.add(inte2).add(inte3);
因为inte1.add(inte2)函数调用的返回值是类Integer的对象,既然是对象,就可以调用其成员函数,所以可以连续调用。
上述代码中,以下代码:
Integer& inc (void)
{
++m_value;
return *this; // 返回自引用
}
在类Integer中定义了一个成员函数inc,该函数返回一个类Integer的引用,而*this就是调用该函数的对象,所以返回的就是调用成员函数的对象的引用。这样的返回值同样可以起到如下代码的作用:
inte1.inc().inc();
注意:
上边两个方法是有区别的,inte1.add(inte2)的返回值是一个临时对象,而inte1.inc()的返回值是对象inte1本身。
8.3 完整代码
本案例的完整代码如下所示:
#include
class Integer
{
private:
int m_value;
public:
Integer(const int& integer):m_value(integer)
{
}
Integer add(Integer const& that)
{
m_value += that.m_value;
return *this; // 返回自拷贝
}
Integer& inc (void)
{
++m_value;
return *this; // 返回自引用
}
void show (void)
{
std::cout << m_value << std::endl;
}
};
int main(int argc, const char * argv[])
{
Integer inte1(10);
Integer inte2(20);
Integer inte3(30);
inte1 = inte1.add(inte2).add(inte3);
inte1.show();
inte1.inc().inc();
inte1.show();
return 0;
}
9 教师和学生
9.1 问题
this指针可以作为函数的实际参数,从一个对象传递给另一个对象,这样能够实现对象间的交互。
9.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:教师
头文件代码如下:
#ifndef Day03__Teacher
#define Day03__Teacher
#include
#include “Student.h”
class Teacher {
public:
void educate (Student* student);
void reply(char const* answer);
};
#endif /* defined(Day03__Teacher) */
上述代码中,以下代码:
void educate (Student* student);
成员函数educate带有一个形参,该形参是类Student的指针。
实现文件代码如下:
#include “Teacher.h”
void Teacher::educate (Student* student)
{
student->ask (“什么是this指针?”, this);
}
void Teacher::reply(char const* answer)
{
std::cout << “老师答:” << answer << std::endl;
}
上述代码中,以下代码:
void Teacher::educate (Student* student)
{
student->ask (“什么是this指针?”, this);
}
在函数educate中调用类Student的成员函数ask,函数ask的第二个实参即为this,此时传入函数ask的是调用函数educate的对象的地址,这样就使得类Teacher和类Student可以互动。
步骤二:学生
头文件代码如下:
#ifndef Day03__Student
#define Day03__Student
#include
class Teacher;
class Student
{
public:
void ask (char const* question, Teacher* teacher);
};
#endif /* defined(Day03__Student) */
上述代码中,以下代码:
void ask (char const* question, Teacher* teacher);
成员函数ask带有两个形参,第二个形参是类Teacher的指针。
实现文件代码如下:
#include “Student.h”
#include “Teacher.h”
void Student::ask (char const* question, Teacher* teacher)
{
std::cout << “学生问:” << question << std::endl;
teacher->reply (“this指针就是指向调用对象的指针”);
}
上述代码中,函数ask中调用类Teacher的成员函数reply。函数ask的第二个形参得到的是类Teacher的this指针,此时传入函数ask的是调用类Teacher的成员函数educate的对象的地址,这样就使得类Teacher和类Student又一次的互动。
9.3 完整代码
Student.h的完整代码如下所示:
#ifndef Day03__Student
#define Day03__Student
#include
class Teacher;
class Student
{
public:
void ask (char const* question, Teacher* teacher);
};
#endif /* defined(Day03__Student) */
Student.cpp的完整代码如下所示:
#include “Student.h”
#include “Teacher.h”
void Student::ask (char const* question, Teacher* teacher)
{
std::cout << “学生问:” << question << std::endl;
teacher->reply (“this指针就是指向调用对象的指针”);
}
Teacher.h的完整代码如下所示:
#ifndef Day03__Teacher
#define Day03__Teacher
#include
#include “Student.h”
class Teacher {
public:
void educate (Student* student);
void reply(char const* answer);
};
#endif /* defined(Day03__Teacher) */
Teacher.cpp的完整代码如下所示:
#include “Teacher.h”
void Teacher::educate (Student* student)
{
student->ask (“什么是this指针?”, this);
}
void Teacher::reply(char const* answer)
{
std::cout << “老师答:” << answer << std::endl;
}
main.cpp的完整代码如下所示:
#include
#include “Teacher.h”
int main(int argc, const char * argv[])
{
Student s;
Teacher t;
t.educate(&s);
return 0;
}
10 常函数和mutable关键字
10.1 问题
在类成员函数的形参表之后,函数体之前加上const关键字,该成员函数的this指针即具有常属性,这样的成员函数被称为常函数。在常函数内部,因为this指针被声明为常量指针,所以无法修改成员变量的值,除非该成员变量被mutable关键字修饰。
10.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:常函数和mutable关键字
头文件代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
mutable unsigned int m_times;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void print (void) const
{
m_times++;
std::cout << “我是” << m_name <<",今年" << m_age << “岁。(第” << m_times << “次打印)” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张飞", 25);
user.print();
user.print();
return 0;
}
上述代码中,以下代码:
void print (void) const
{
m_times++;
std::cout << "我是" << m_name <<",今年" << m_age << "岁。(第" << m_times << "次打印)" << std::endl;
}
是类User的一个成员函数,值得注意的是在函数头的最后有一个const,当一个成员函数有此const时,此函数被称为常函数。在常函数内部,因为this指针被声明为常量指针,所以无法修改成员变量的值,除非该成员变量被mutable关键字修饰。如果在print函数中添加以下语句:
m_name = "";
m_age = 0;
是非法的,因为this->m_name和this->m_age是只读型变量。但函数中以下语句:
m_times++;
是合法的,因为成员变量m_times在声明的时候添加了mutable关键字,如下语句:
private:
std::string m_name;
int m_age;
mutable unsigned int m_times;
mutable关键字会在常函数中此成员变量m_times被使用的语句中添加隐式转换,将this指针临时转换成普通指针。
10.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
mutable unsigned int m_times;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void print (void) const
{
m_times++;
std::cout << “我是” << m_name <<",今年" << m_age << “岁。(第” << m_times << “次打印)” << std::endl;
}
};
int main(int argc, const char * argv[])
{
User user("张飞", 25);
user.print();
user.print();
return 0;
}
11 通过常/非常对象调用常/非常函数
11.1 问题
常对象是指被const关键字修饰的对象、对象指针或对象引用,非常对象就是没有被const关键字修饰的。通过常对象调用成员函数,传递给该成员函数this指针参数的实参是一个常量指针。
11.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:通过常/非常对象调用常/非常函数
头文件代码如下:
#include
class User
{
private:
std::string m_name;
int m_age;
mutable unsigned int m_times;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void modify (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void print (void) const
{
m_times++;
std::cout << “我是” << m_name <<",今年" << m_age << “岁。(第” << m_times << “次打印)” << std::endl;
}
};
int main(int argc, const char * argv[])
{
const User user("张飞", 25);
//user.modify("关羽", 28);
user.print();
user.print();
User user1("张飞", 25);
user1.modify("关羽", 28);
user1.print();
return 0;
}
上述代码中,以下代码:
const User user("张飞", 25);
定义了一个常对象。常对象无法调用非常函数,如以下语句所示:
//user.modify("关羽", 28);
因为成员函数modify,在以下语句:
void modify (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
没有说明成一个常函数。
常对象只能调用常函数,如以下语句所示:
user.print();
注意:
如果在成员变量声明时,添加了mutable关键字,如以下语句所示:
private:
std::string m_name;
int m_age;
mutable unsigned int m_times;
成员变量m_times添加了mutable关键字,则该变量在常函数中仍可改变。如以下语句所示:
user.print();
user.print();
上述代码中,以下代码:
User user1("张飞", 25);
user1.modify("关羽", 28);
user1.print();
定义了一个非常对象user1,它既可以调用非常函数modify,也可以调用常函数print。
11.3 完整代码
本案例的完整代码如下所示:
#include
class User
{
private:
std::string m_name;
int m_age;
mutable unsigned int m_times;
public:
User (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void modify (std::string const& name, int age)
{
m_name = name;
m_age = age;
}
void print (void) const
{
m_times++;
std::cout << “我是” << m_name <<",今年" << m_age << “岁。(第” << m_times << “次打印)” << std::endl;
}
};
int main(int argc, const char * argv[])
{
const User user("张飞", 25);
//user.modify("关羽", 28);
user.print();
user.print();
User user1("张飞", 25);
user1.modify("关羽", 28);
user1.print();
return 0;
}
12 常函数与非常函数的重载匹配
12.1 问题
原型相同的成员函数,常函数版本和非常函数版本能够构成重载函数。常对象只能选择常函数版本;非常对象优先选择非常函数版本,如果没有非常函数版本,也能选择常函数版本。
12.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:常函数与非常函数的重载匹配
头文件代码如下:
#include
class Array
{
private:
int *m_array;
size_t m_size;
public:
Array (const int& size)
{
m_size = size;
m_array = new int[m_size];
}
size_t size (void) const
{
return m_size;
}
const int& at (size_t index) const
{
return m_array[index];
}
int& at (size_t index)
{
return m_array[index];
}
};
void init (Array& array, int elm)
{
for (size_t i = 0; i < array.size (); ++i)
array.at (i) = elm;
}
void print (Array const& array)
{
for (size_t i = 0; i < array.size (); ++i)
std::cout << array.at (i) << " ";
std::cout<< std::endl;
}
int main(int argc, const char * argv[])
{
Array array(10);
init(array, 5);
print(array);
return 0;
}
上述代码中,以下代码:
const int& at (size_t index) const
{
return m_array[index];
}
int& at (size_t index)
{
return m_array[index];
}
函数at为重载函数。虽然函数at的参数完全相同,但第一个函数是常函数,第二个函数为非常函数。常函数与非常函数可以构成重载。
上述代码中,以下代码:
void print (Array const& array)
{
for (size_t i = 0; i < array.size (); ++i)
std::cout << array.at (i) << " ";
std::cout<< std::endl;
}
由于对象array是常对象,由于常对象只能调用类中的常函数,所以下述代码中:
std::cout << array.at (i) << " ";
函数at只能调用常版本的函数at。
上述代码中,以下代码:
void init (Array& array, int elm)
{
for (size_t i = 0; i < array.size (); ++i)
array.at (i) = elm;
}
由于对象array是非常对象,由于非常对象既能调用类中的非常函数,又能调用类中的非常函数,但优先调用非常版本,所以下述代码中:
array.at (i) = elm;
函数at会优先调用非常版本的函数at。
注意:非常版本at的返回值是引用,即array.at (i)是m_array[index]的别名,所以等效于m_array[index] = elm。
12.3 完整代码
本案例的完整代码如下所示:
#include
class Array
{
private:
int *m_array;
size_t m_size;
public:
Array (const int& size)
{
m_size = size;
m_array = new int[m_size];
}
size_t size (void) const
{
return m_size;
}
const int& at (size_t index) const
{
return m_array[index];
}
int& at (size_t index)
{
return m_array[index];
}
};
void init (Array& array, int elm)
{
for (size_t i = 0; i < array.size (); ++i)
array.at (i) = elm;
}
void print (Array const& array)
{
for (size_t i = 0; i < array.size (); ++i)
std::cout << array.at (i) << " ";
std::cout<< std::endl;
}
int main(int argc, const char * argv[])
{
Array array(10);
init(array, 5);
print(array);
return 0;
}
13 析构函数的基本用法
13.1 问题
析构函数是特殊的成员函数。它的函数名就是在类名前面加“~”,没有返回类型,没有参数,也不能重载。析构函数在销毁对象时自动被调用,析构函数在对象的整个生命周期内,最多被调用一次。
13.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:析构函数的基本用法
代码如下:
#include
class Array {
private:
int* m_array;
public:
Array (size_t size) : m_array (new int[size])
{
}
~Array (void)
{
delete[] m_array;
}
};
int main(int argc, const char * argv[])
{
Array array(10);
return 0;
}
上述代码中,以下代码:
~Array (void)
{
delete[] m_array;
}
定义了一个析构函数,其函数名与构造函数一样,为了与构造函数相区分前面加了一个~号。析构函数是没有返回值,没有参数的函数,因为没有参数,所以析构函数不能被重载。
析构函数同构造函数一样,是自动被调用的,构造函数是在对象的存储空间分配完毕时被调用的,而析构函数则是在对象的存储空间被销毁前调用的。
析构函数可以被用来销毁释放在对象的构造过程或生命周期内所获得的资源。如本例中,在构造函数中new的数组m_array,在析构函数中将其释放。
13.3 完整代码
本案例的完整代码如下所示:
#include
class Array {
private:
int* m_array;
public:
Array (size_t size) : m_array (new int[size])
{
}
~Array (void)
{
delete[] m_array;
}
};
int main(int argc, const char * argv[])
{
Array array(10);
return 0;
}
14 析构函数负责总善后
14.1 问题
析构函数的功能不仅限于释放资源,它可以执行任何作为类的设计者希望在最后一次使用对象之后执行的动作。
14.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:析构函数负责总善后
代码如下:
#include
class Child;
class Parent
{
private:
int m_count;
public:
Parent()
{
m_count = 0;
}
void reg (Child* child)
{
m_count++;
}
void unreg (Child* child)
{
m_count–;
}
};
class Child
{
private:
Parent* m_parent;
public:
Child (Parent* parent) : m_parent (parent)
{
m_parent->reg (this);
}
~Child (void)
{
m_parent->unreg (this);
}
};
int main(int argc, const char * argv[])
{
Parent parent;
Child child(&parent);
return 0;
}
上述代码中,以下代码:
Child (Parent* parent) : m_parent (parent)
{
m_parent->reg (this);
}
定义了类Child的构造函数,在该函数中当生成一个类Child的对象时,需要在类Parent中进行注册。
上述代码中,以下代码:
~Child (void)
{
m_parent->unreg (this);
}
定义了类Child的析构函数,因为当类Child的对象在生成时需要在类Parent中注册,所以当类Child的对象销毁前,需要在类Parent中反注册。
14.3 完整代码
本案例的完整代码如下所示:
#include
class Child;
class Parent
{
private:
int m_count;
public:
Parent()
{
m_count = 0;
}
void reg (Child* child)
{
m_count++;
}
void unreg (Child* child)
{
m_count–;
}
};
class Child
{
private:
Parent* m_parent;
public:
Child (Parent* parent) : m_parent (parent)
{
m_parent->reg (this);
}
~Child (void)
{
m_parent->unreg (this);
}
};
int main(int argc, const char * argv[])
{
Parent parent;
Child child(&parent);
return 0;
}
15 缺省析构函数不释放动态分配的资源
15.1 问题
通常情况下,如果对象在其生命周期的最终时刻,并不持有任何动态分配的资源,也没有任何善后工作可做,那么完全可以不为其定义析构函数。
如果一个类没有定义析构函数,那么编译器会为其提供一个缺省析构函数,该函数对基本类型的成员变量,什么也不做,对类类型的成员变量和基类子对象,调用相应类型的析构函数。
15.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:缺省析构函数不释放动态分配的资源
设置循环,将待排序数组中的数组元素两两进行比较,如果有不符合要求的顺序的,进行交换。
代码如下所示:
#include
class Array {
private:
int* m_array;
public:
Array (size_t size) : m_array (new int[size])
{
}
};
int main(int argc, const char * argv[])
{
Array array(10);
return 0;
}
上述代码中,类Array没有定义析构函数,那么,编译器会为该类自动添加一个缺省析构函数,代码如下所示:
~Array (void)
{
}
该缺省析构函数的函数体内没有任何代码,所以如果在对象的构造过程或生命周期内获得过任何资源,都必须手动添加析构函数,不能使用缺省析构函数,否则就会造成内存泄露。
15.3 完整代码
本案例的完整代码如下所示:
#include
class Array {
private:
int* m_array;
public:
Array (size_t size) : m_array (new int[size])
{
}
};
int main(int argc, const char * argv[])
{
Array array(10);
return 0;
}
16 缺省拷贝构造函数仅支持浅拷贝
16.1 问题
缺省方式的拷贝构造和拷贝赋值,对包括指针在内的基本类型成员变量按字节复制,导致浅拷贝问题。
16.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将变成野指针。
16.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;
}
17 自定义拷贝构造函数支持深拷贝
17.1 问题
为了获得完整意义上的对象副本,必须自己定义拷贝构造和拷贝赋值,针对指针型成员变量做深拷贝。
17.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;
}
User (User const &user)
{
m_name = new char[strlen(user.m_name) + 1];
strcpy(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 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指向的空间仍然存在。
17.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;
}
User (User const &user)
{
m_name = new char[strlen(user.m_name) + 1];
strcpy(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;
}
18 支持深拷贝的拷贝赋值运算符函数
18.1 问题
C++中预定义的赋值运算符的操作对象只能是基本数据类型。但实际上,对于许多用户自定义类型(例如类),也需要赋值运算操作。这时就必须在C++中重新定义赋值运算符,赋予赋值运算符新的功能,使它能够用于用户自定义类型执行用户自定义的操作。
18.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;
}
User (User const &user)
{
m_name = new char[strlen(user.m_name) + 1];
strcpy(m_name, user.m_name);
m_age = user.m_age;
}
User& operator = (User const &user)
{
if (&user != this)
{
char* psz = new char[strlen (user.m_name) + 1];
delete[] m_name;
m_name = strcpy (psz, user.m_name);
}
return *this;
}
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("李四", 25);
user1 = user;
user1.who();
return 0;
}
上述代码中,以下代码:
User& operator = (User const &user)
{
if (&user != this)
{
char* psz = new char[strlen (user.m_name) + 1];
delete[] m_name;
m_name = strcpy (psz, user.m_name);
}
return *this;
}
定义了一个赋值运算符的重载函数。因为,赋值运算符在C++中只能是操作基本数据类型,对于类User的对象,赋值运算符对以下语句:
user1 = user;
只是简单地让user1.m_name指向了user.m_name指向的空间,这样带来的隐患与浅拷贝相同,如果user对象先释放了,则user1.m_name将变成野指针。为了避免这一现象的发生需要重载赋值运算符,赋予赋值运算符深拷贝的功能。在这一过程中,以下语句:
if (&user != this)
的作用是,如果执行了自身赋值,那么就什么都不做,直接返回。
以下语句:
char* psz = new char[strlen (user.m_name) + 1];
delete[] m_name;
m_name = strcpy (psz, user.m_name);
先分配新资源再释放旧资源是有意义的,即使目标对象的新资源分配失败,其原有的内容也不至于遭到破坏
最后应该说明的是,这个赋值运算符函数的返回值是User&,目的是满足赋值表达式的语义,即当有以下语句时:
user = user1 = user2;
user1 = user2的返回值仍是一个类User的对象,即user1,这样可以继续赋值给user。
18.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;
}
User (User const &user)
{
m_name = new char[strlen(user.m_name) + 1];
strcpy(m_name, user.m_name);
m_age = user.m_age;
}
User& operator = (User const &user)
{
if (&user != this)
{
char* psz = new char[strlen (user.m_name) + 1];
delete[] m_name;
m_name = strcpy (psz, user.m_name);
}
return *this;
}
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("李四", 25);
user1 = user;
user1.who();
return 0;
}