第五章 析构函数、命名空间、继承、成员访问权限、初始化列表
析构函数(Destructor)
- 析构函数(也叫析构器),在对象销毁的时候自动调用,一般用于完成对象的清理工作
- 特点
- 函数名以~开头,与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个析构函数
- 注意
- 通过malloc分配的对象free的时候不会调用析构函数
-
构造函数、析构函数要声明为public,才能被外界正常使用
struct Person { int m_age; //对象创建完毕的时候调用 Person() { cout << "Person()" << endl; this->m_age = 0; } //对象被销毁的时候调用 ~Person() { cout << "~Person()" << endl; } }; int main() { //栈 { Person person; } //堆 Person *p = new Person; delete p; getchar(); return 0; }
对象的内存管理
- 对象内部申请的堆空间,由对象内部回收
- 多注意setter和析构的内存管理
-
注意点1:类类型的成员函数是指针与是类的区别
struct Car { Car() { cout << "car()" << endl; } ~Car(){ cout << "~Car()" << endl; } }; struct Person { int m_age; //Car m_car;//这么写,在创建person对象的时候会创建一个Car对象 Car *m_car;//这么写,在创建person对象的时候只会创建一个指针变量,占4个字节,不会创建Car对象 Person() { cout << "Person()" << endl; } //内存回收、清理工作 ~Person() { cout << "~Person()" << endl; } }; int main(){ Person *p = new Person; delete p; getchar(); return 0; }
-
在构造函数内部分配内存,需要在析构函数内部释放
struct Car { int m_price; Car() { cout << "car()" << endl; } ~Car(){ cout << "~Car()" << endl; } }; struct Person { int m_age; Car *m_car; Person() { cout << "Person()" << endl; this->m_car = new Car(); } //内存回收、清理工作 ~Person() { cout << "~Person()" << endl; delete this->m_car; } }; //使用 Person *p = new Person(); delete p;
-
在setter方法内部分配内存,同时在setter方法内部释放,但是调用释放在析构函数中
struct Person { private: char *m_name; public: void setName( const char *name) { //重复设置,返回 if (this->m_name == name) return; //释放旧空间 if (this->m_name != NULL){ delete[] this->m_name; this->m_name = NULL; } //开辟新空间 if (name != NULL){ //为何+1,因为字符串默认有个\0字符,占一个字节,strlen只计算的是不带\0的长度 this->m_name = new char[strlen(name) + 1](); strcpy(this->m_name, name); } } const char *getName() { return this->m_name; } Person() { memset(this, 0, sizeof(Person)); } ~Person(){ this->setName(NULL); } };
-
声明和实现分离
-
类的声明写到.h文件,类的实现写到.cpp;当然也可以都写在一个文件中
//Person.h #pragma once class Person { public: Person(); ~Person(); void setAge(int age); int getAge(); private: int m_age; } //Person.cpp #include<iostream> #include"Person.h" using namespace std; Person::Person(){ cout << "person()" << endl; } Person::~Person(){ cout << "~Person()" << endl; } void Person::setAge(int age) { this->m_age = age; cout << "setAge" << endl; } int Person::getAge() { cout << "getAge" << endl; return this->m_age; } int main() { Person person; person.setAge(10); cout << person.getAge() << endl; getchar(); return 0; }
命名空间
-
命名空间可以用来避免命名冲突
class Person { public: int m_age; }; //全局变量 int g_no; //函数 void test() { } //命名空间 namespace ZH { //全局变量 int g_no; //函数 void test() { } class Person { public: int m_age; }; } int main() { Person person; person.m_age = 10; ZH::Person person2; person2.m_age = 20; g_no = 30; ZH::g_no = 40; test(); ZH::test(); cout << "person.m_age=" << person.m_age << endl; cout << "person2.m_age=" << person2.m_age << endl; getchar(); return 0; }
using namespace 空间名;
- 声明我要用“空间名”中的东西,统一声明,不用在使用的时候还要每次写
//命名空间 namespace ZH { //全局变量 int g_no; //函数 void test() { } class Person { public: int m_age; }; } //统一使用 using namespace ZH; g_no = 10; Person person; test(); //指定用命名空间的哪一个 using ZH::g_no; g_no = 10;
- 命名空间不影响内存布局
命名空间的嵌套
-
举例:
namespace ZH { namespace SS { int g_age; } } int main() { ZH::SS::g_age = 10; using namespace ZH::SS; g_age = 20; using ZH::SS::g_age; g_age = 30; }
-
有个默认的全局命名空间,我们创建的命名空间默认都嵌套在它里面
int g_no; namespace ZH { namespace SS { int g_age; } } int main() { //下面2个等价 g_no = 10; //默认命名空间::,全局命名空间 ::g_no = 10; using namespace ZH::SS; g_age = 20; }
命名空间的合并
-
以下2种写法是等价的
//写法1 namespace ZH { int g_age; } namespace ZH { int g_no; } //等价于写法2 namespace ZH { int g_age; int g_no; }
-
命名空间用在类中
//Person.h #pragma once namespace ZH { class Person { public: Person(); ~Person(); }; } //Person.cpp #include "Person.h" namespace ZH { Person::Person(){ } Person::~Person(){ } } //main函数使用 #include<iostream> #include"Person.h" using namespace ZH; using namespace std; int main() { Person person; return 0; }
继承
-
继承,可以让子类拥有父类的所有成员(变量\函数)
struct Person { int m_age; void run() { cout << "run()" << endl; } }; //继承Person struct Student : Person { int m_no; void study() { cout << "study()" << endl; } }; int main() { Student student; student.m_age = 10; student.m_no = 22; student.run(); student.study(); getchar(); return 0; }
- 关系描述
- Student是子类(subclass,派生类)
- Person是父类(superclass,超类)
- C++中没有像Java、Objective-C的基类
- Java:java.lang.Object
- Objective-C:NSObject
- 关系描述
对象的内存布局
成员访问权限
- 成员:成员变量、成员函数
- 成员访问权限、继承方式有3种
- public:公共的,任何地方都可以访问(struct默认)
- protected:子类内部、当前类内部可以访问
- prvate:私有的,只有当前类内部可以访问(class默认)
struct Person { //public: protected: //private: int m_age; void run() { cout << "run()" << endl; //当前类的成员由public或者protected或者private修饰,在当前类内部可以访问成员。 this->m_age = 0; } }; struct Student : Person { int m_no; void study() { //当父类的成员为public或者protected时,子类可以访问父类的成员 //如果父类成员为private,子类不可以访问 this->m_age = 20; this->run(); } }; int main() { Person person; //当前类的成员被protected或private修饰时,外部不可以访问。 person.m_age = 10; //当父类的子成员被修饰为protected或private,外部不可以访问。 Student student; student.m_age = 10; getchar(); return 0; }
- 从上面说明继承也有三种方式public、protected、private
- class创建的类默认继承方式是private
- struct创建的类默认继承方式是public
- 子类内部访问父类成员的权限,是以下2项中权限最小的那个
- 2项指:
- 成员本身的访问权限
- 上一级父类的继承方式
-
即:2项指同时有成员权限、继承权限
struct Person { public: int m_age; void run() { cout << "run()" << endl; this->m_age = 0; } }; struct Student : private Person { int m_no; void study() { //但是子类内部仍然可以访问父类的成员!!! this->m_age = 20; this->run(); } }; struct GoodStudent :Student { int m_money; void test() { //不能调用 //this->m_age = 30; //this->run(); this->study(); this->m_no = 55; } }; //尽管父类的成员是public,但是有与子类继承是private,那么二者取最小权限private Student student; //不能访问 //student.m_age = 10; //可以访问 student.study(); student.m_no = 66;
- 从上面的3个类中我们可以看出
- Person成员为public
- Student的继承方式为private,但是
- Student内部可以访问父类Person的成员
- 外部不能通过Student访问其父类成员
- 外部可以通过Student 访问当前类的成员,说明当前类的权限仍然是默认的public
- GoodStudent的内部不能访问Person的成员
- 结论:继承权限本质就是将父类的成员拿到子类,并全部把父类的成员权限设置为同继承权限一样的权限,但是不会影响当前类的成员权限
-
上面整合成一个类就是如下:
//本质 struct GoodStudent :Student { //Person的成员,因为为private继承 private: int m_age; void run() { cout << "run()" << endl; this->m_age = 0; } public: //Student的成员,因为Student为Struct创建,因此默认为public继承 int m_no; void study() { //但是子类内部仍然可以访问父类的成员 this->m_age = 20; this->run(); } //CoodStudent自己的成员 int m_money; void test() { //不能调用 //this->m_age = 30; //this->run(); this->study(); this->m_no = 55; } };
- 从上面的3个类中我们可以看出
- 2项指:
- 开发中用的最多的继承方式是public,这样能保留父类原来的成员访问权限
- 访问权限不影响对象的内存布局
初始化列表
- 特点
- 一种便捷的初始化成员变量的方式
- 只能用在构造函数中
- 初始化顺序只跟成员变量的声明顺序有关
-
示例代码
struct Person { int m_age; int m_height; Person() { this->m_age = 0; this->m_height = 0; } //初始化列表 Person(int age, int height) : m_age(age),m_height(height) { } //这么写仍然是先赋值age,只跟成员变量声明顺序有关 //Person(int age, int height) :m_height(height),m_age(age){} //由反汇编看出:这么写等价于初始化列表 /*Person(int age, int height) { this->m_age = age; this->m_height = height; }*/ void display() { cout << "age is " << this->m_age << endl; cout << "height is " << this->m_height << endl; } }; Person person1; person1.display(); Person person(10,20); person.display();
构造函数的互相调用
-
案例:
struct Person { int m_age; int m_height; Person() { cout << "Person()" << endl; //直接调用构造函数,这个不能初始化成功!!! //因为这就相当于又重新创建了一个临时对象 ,等价于Person tem(0,0);这是一个新的对象 Person(0, 0); } Person(int age, int height) { cout << "Person(int,int)" << endl; this->m_age = age; this->m_height = height; } void display() { cout << "age is " << this->m_age << endl; cout << "height is " << this->m_height << endl; } }; int main() { Person person; person.display(); } //打印结果 Person() Person(int, int) age is - 858993460 height is - 858993460
- 可以看见Person()、 Person(int, int)都调用了,那为什么没有初始化成功呢?(可以由反汇编分析)
- 构造函数是对象被创建完毕后调用的,即先分配对象内存,然后调用构造函数
- 调用构造函数时,会将当前对象的地址传递给Person()内部的this指针
- 构造函数内部调用构造函数Person(0,0);这就相当于又重新创建了一个临时对象,等价于 Person tem(0,0);
- 调用构造函数Person(0,0)时,传递给Person(0,0)构造函数内部的this指针是那个临时对象的指针(tem);
- 那么Person(0,0)构造函数内部的this指针指向的可不是原来对象的地址了,而是Person()内部通过Person(0,0)创建的临时对象的地址值,并不是Person()内部this指针指向的对象地址
- 因此,Person(0,0)内部使用this赋值肯定不是原对象的值
- 可以看见Person()、 Person(int, int)都调用了,那为什么没有初始化成功呢?(可以由反汇编分析)
-
结论:构造函数调用构造函数必须在初始化列表中调用
- 把上面的构造函数Person(),修改为如下就可以调用成功了
Person() : Person(0,0){ cout << "Person()" << endl; }
- 这么调用就会把Person()内部的this指针指向的地址值(原对象的地址)传递给Person(int,int)
- 那么Person(int,int)内部的this指针指向的就是原来的对象了,就可以初始化成功了。
初始化列表与默认参数配合使用
-
举例
class Person { int m_age; int m_height; public: Person(int age = 0,int height = 0):m_age(age),m_height(height){ } }; //使用 Person perosn1; Person person2(18); Person person3(18, 190);
-
如果函数声明和实现是分离的
- 初始化列表只能写在函数的实现中
- 默认参数只能写在函数的声明中(前面讲过)
class Person { int m_age; int m_height; public: //默认参数只能写在声明中 Person(int age = 0, int height = 0); }; //构造函数的初始化列表只能写在实现中 Person::Person(int age, int height) :m_age(age), m_height(height){ } //使用 //注意:这个为何不报错??? 前面讲到,一旦手动实现构造函数,而且没有实现不带参数的构造函数,这么创建就会报错,说明这里一定产生了默认无参的构造函数,那么这就是特殊情况下???---不是,这里调用的不是无参的构造函数,而是等价于 Person(0,0); Person perosn1; //等价于Person(0,0);而不是Person(); Person person2(18); Person person3(18, 190);
父类的构造函数
- 子类的构造函数默认会先调用父类的无参构造函数
-
如果子类的构造函数显式地调用了父类的有参构造函数,就不会再去默认调用父类的无参构造函数
class Person { int m_age; public: Person() { cout << "Person()" << endl; } Person(int age) : m_age(age) { cout << "Person( int)" << endl; } }; class Student :Person { int m_score; public: Student() { cout << "Student()" << endl; } Student(int age, int score) :m_score(score),Person(age) { //不能这么调用,否则就是创建一个临时Person对象了,不是原来的对象了 //Person(age) cout << "Student( int ,int )" << endl; } Student(int age) :m_score(m_score) { cout << "Student( int )" << endl; } }; //除了调用当前类的无参构造函数,而且会先调用父类的无参构造函数 Student student; //不会调用父类无参的构造函数 Student student2(18, 180); //还是会调用父类无参的构造函数 Student student3(120);
-
如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数
class Person { int m_age; public: Person(int age) : m_age(age) { cout << "Person( int)" << endl; } }; class Student :Person { int m_score; public: //根本不让你实现,因为父类没有无参构造函数 /*Student() { cout << "Student()" << endl; } */ /*Student(int age) :m_score(m_score) { cout << "Student( int )" << endl; }*/ //子类的构造函数必须显式调用父类的有参构造函数 Student(int age, int score) :m_score(score),Person(age) { //不能这么调用,否则就是创建一个临时Person对象了,不是原来的对象了 //Person(age) cout << "Student( int ,int )" << endl; } }; //使用 Student student(18, 99);
继承体系下的构造函数相互调用示例
class Person {
int m_age;
public:
Person():Person(0) {
}
Person(int age) : m_age(age) {
}
};
class Student :Person {
int m_score;
public:
Student() :Student(0,0) {
cout << "Student()" << endl;
}
Student(int age, int score) :m_score(score),Person(age) {
//不能这么调用,否则就是创建一个临时Person对象了,不是原来的对象了
//Person(age)
}
};
构造、析构顺序
-
构造和析构的顺序相反
struct Person { Person() { cout << "Person()" << endl; } ~Person(){ cout << "~Person()" << endl; } }; struct Student :Person { Student() { cout << "Student()" << endl; } ~Student(){ cout << "~Student()" << endl; } }; //调用 Student *p = new Student; delete p; //打印 Person() Student() ~Student() ~Person()